/*****************************************************************

Copyright (c) 2001 Matthias Elter <elter@kde.org>
Copyright (c) 2002 John Firebaugh <jfirebaugh@kde.org>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.#

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

******************************************************************/

#include <assert.h>

#include <qbitmap.h>
#include <qcolor.h>
#include <qimage.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qstyle.h>
#include <qstylesheet.h>
#include <qtooltip.h>

#include <kapplication.h>
#include <kdebug.h>
#include <kglobalsettings.h>
#include <klocale.h>
#include <kiconeffect.h>
#include <kiconloader.h>
#include <kimageeffect.h>

#include "taskmanager.h"
#include "taskbar.h"
#include "taskbarsettings.h"
#include "tasklmbmenu.h"
#include "taskrmbmenu.h"

#include "taskcontainer.h"
#include "taskcontainer.moc"

QImage TaskContainer::blendGradient = QImage();
TaskBar::Action TaskContainer::leftButtonAction = TaskBar::ShowTaskList;
TaskBar::Action TaskContainer::middleButtonAction = TaskBar::ActivateRaiseOrIconify;
TaskBar::Action TaskContainer::rightButtonAction = TaskBar::ShowOperationsMenu;

TaskContainer::TaskContainer( Task::Ptr task, TaskBar* bar, QWidget *parent, const char *name )
    : QToolButton(parent, name),
      lastActivated(0),
      m_menu(0),
      arrowType(Qt::UpArrow),
      taskBar(bar),
      discardNextMouseEvent(false),
      aboutToActivate(false)
{
    init();

    tasks.append( task );
    updateFilteredTaskList();
    sid = task->classClass();
    setAcceptDrops(true); // Always enabled to activate task during drag&drop.

    connect( task, SIGNAL( changed() ), SLOT( taskChanged() ) );
    connect( task, SIGNAL( iconChanged() ), SLOT( iconChanged() ) );
    connect( task, SIGNAL( activated() ), SLOT( setLastActivated() ) );

    checkAttention( task );
}

TaskContainer::TaskContainer( Startup::Ptr startup, PixmapList *startupFrames, TaskBar* bar, QWidget *parent, const char *name )
    : QToolButton(parent, name),
      lastActivated(0),
      m_menu(0),
      arrowType(Qt::LeftArrow),
      taskBar(bar),
      discardNextMouseEvent(false),
      aboutToActivate(false)
{
    init();

    startups.append( startup );
    sid = startup->bin();

    frames = startupFrames;

    connect( startup, SIGNAL( changed() ), SLOT( update() ) );
    animationTimer.start( 100 );
}

void TaskContainer::init()
{
    setBackgroundMode( NoBackground );
    connect( this, SIGNAL( clicked() ), SLOT( slotClicked() ) );
    QToolTip::add( this, "<qt>" + QStyleSheet::escape(name()) + "</qt>" );
    animBg = QPixmap( 16, 16 );

    // timers
    connect( &animationTimer, SIGNAL( timeout() ), SLOT( animationTimerFired() ) );
    connect( &dragSwitchTimer, SIGNAL( timeout() ), SLOT( dragSwitch() ) );
    connect( &attentionTimer, SIGNAL( timeout() ), SLOT( attentionTimerFired() ) );
    currentFrame = 0;
    frames = 0;
    attentionState = -1;
}

TaskContainer::~TaskContainer()
{
    animationTimer.stop();
    dragSwitchTimer.stop();
}

void TaskContainer::taskChanged()
{
    const QObject* source = sender();
    Task::Ptr task = 0;
    TaskList::const_iterator itEnd = tasks.end();
    for (TaskList::const_iterator it = tasks.begin(); it != itEnd; ++it)
    {
        if ((*it) == source)
        {
            task = *it;
            break;
        }
    }

    if (task)
    {
        checkAttention( task );
    }

    update();
}

void TaskContainer::iconChanged()
{
    const QObject* source = sender();
    Task::Ptr task = 0;
    TaskList::const_iterator itEnd = tasks.end();
    for (TaskList::const_iterator it = tasks.begin(); it != itEnd; ++it)
    {
        if ((*it) == source)
        {
            task = *it;
            break;
        }
    }

    if (task && task != m_filteredTasks.first())
    {
        if (m_menu)
        {
            m_menu->update();
        }
        return;
    }
    QToolButton::update();
}

void TaskContainer::update()
{
    QString tooltip = "<qt>" + QStyleSheet::escape(name()) + "</qt>";
    if (QToolTip::textFor(this) != tooltip)
    {
        QToolTip::remove( this );
        QToolTip::add( this, tooltip );
    }

    QToolButton::update();
}

void TaskContainer::setLastActivated()
{
    TaskList::iterator itEnd = m_filteredTasks.end();
    for (TaskList::iterator it = m_filteredTasks.begin(); it != itEnd; ++it)
    {
        Task::Ptr t = *it;
        if ( t->isActive() )
        {
            lastActivated = t;
            return;
        }
    }
    lastActivated = 0L;
}


void TaskContainer::animationTimerFired()
{
    if (frames && taskBar->showIcon()) {
        QPixmap *pm = frames->at( currentFrame );

        // draw pixmap
        if ( pm && !pm->isNull() ) {
	    // we only have to redraw the background for frames 0, 8 and 9
	    if ( currentFrame == 0 || currentFrame > 7 ) {
		// double buffered painting
		QPixmap composite( animBg );
		bitBlt( &composite, 0, 0, pm );
		bitBlt( this, iconRect.x(), iconRect.y(), &composite );
    	    }
	    else
		bitBlt( this, iconRect.x(), iconRect.y(), pm );
	}

        // increment frame counter
        if ( currentFrame >= 9)
	    currentFrame = 0;
        else
	    currentFrame++;
    }
}

void TaskContainer::checkAttention( const Task::Ptr t )
{
    bool attention = t ? t->demandsAttention() : false;
    if( attention && attentionState == -1 ) // was activated
    {
        attentionState = 0;
        attentionTimer.start( 500 );
    }
    else if( !attention && attentionState >= 0 )
    { // need to check all
        TaskList::iterator itEnd = m_filteredTasks.end();
        for (TaskList::iterator it = m_filteredTasks.begin(); it != itEnd; ++it)
        {
            if ( (*it)->demandsAttention())
            {
                attention = true;
                break;
            }
        }

        if( !attention )
        {
            attentionTimer.stop();
            attentionState = -1;
        }
    }
}

void TaskContainer::attentionTimerFired()
{
    assert( attentionState != -1 );
    if( attentionState < ATTENTION_BLINK_TIMEOUT )
        ++attentionState;
    else
        attentionTimer.stop();
    update();
}

QSizePolicy TaskContainer::sizePolicy() const
{
    return QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
}

void TaskContainer::resizeEvent( QResizeEvent * )
{
    // calculate the icon rect
    QRect br( style().subRect( QStyle::SR_PushButtonContents, this ) );
    iconRect = QStyle::visualRect( QRect(br.x() + 2, (height() - 16) / 2, 16, 16), this );
}

void TaskContainer::add( Task::Ptr task )
{
    if ( !task ) return;

    tasks.append( task );
    updateFilteredTaskList();
    connect( task, SIGNAL( changed() ), SLOT( taskChanged() ) );
    connect( task, SIGNAL( iconChanged() ), SLOT( iconChanged() ) );
    connect( task, SIGNAL( activated() ), SLOT( setLastActivated() ) );
    if ( sid.isEmpty() )
        sid = task->classClass();
    checkAttention( task );

    update();
}

void TaskContainer::add( Startup::Ptr startup )
{
    if ( !startup ) return;

    startups.append( startup );
    if ( sid.isEmpty() )
        sid = startup->bin();

    connect( startup, SIGNAL( changed() ), SLOT( update() ) );
    if ( !animationTimer.isActive() )
        animationTimer.start( 100 );

    update();
}

void TaskContainer::remove( Task::Ptr task )
{
    if ( !task ) return;

    tasks.remove( task );
    updateFilteredTaskList();
    checkAttention();

    update();
}

void TaskContainer::remove( Startup::Ptr startup )
{
    if ( !startup ) return;

    startups.remove( startup );
    if ( startups.isEmpty() )
        animationTimer.stop();

    update();
}

bool TaskContainer::contains( Task::Ptr task )
{
    if ( !task ) return false;
    return ( tasks.find( task ) != tasks.end() );
}

bool TaskContainer::contains( Startup::Ptr startup )
{
    if ( !startup ) return false;
    return ( startups.find( startup ) != startups.end() );
}

bool TaskContainer::contains( WId win )
{
    TaskList::iterator itEnd = tasks.end();
    for (TaskList::iterator it = tasks.begin(); it != itEnd; ++it)
    {
        if ( (*it)->window() == win )
            return true;
    }
    return false;
}

bool TaskContainer::isEmpty()
{
    return ( tasks.isEmpty() && startups.isEmpty() );
}

QString TaskContainer::id()
{
    return sid;
}

QColor TaskContainer::blendColors( QColor c1, QColor c2 )
{
    int r1, g1, b1;
    int r2, g2, b2;

    c1.rgb( &r1, &g1, &b1 );
    c2.rgb( &r2, &g2, &b2 );

    r1 += (int) ( .5 * ( r2 - r1 ) );
    g1 += (int) ( .5 * ( g2 - g1 ) );
    b1 += (int) ( .5 * ( b2 - b1 ) );

    return QColor( r1, g1, b1 );
}

void TaskContainer::drawButton( QPainter *p )
{
    // get a pointer to the pixmap we're drawing on
    QPixmap *pm( (QPixmap*)p->device() );
    QPixmap pixmap; // icon
    Task::Ptr task = NULL;
    bool iconified = true;
    QFont font( KGlobalSettings::taskbarFont() );

    // draw sunken if we contain the active task
    bool active = false;
    bool demands_attention = false;
    TaskList::iterator itEnd = m_filteredTasks.end();
    for (TaskList::iterator it = m_filteredTasks.begin(); it != itEnd; ++it)
    {
        task = *it;
	if ( ! task->isIconified() )
	    iconified = false;
	if ( task->isActive() )
	    active = true;
        if ( task->demandsAttention() )
            demands_attention = true;
    }

    if ( active )
	font.setBold( true );

    QColorGroup colors = colorGroup();
    if (demands_attention &&
        (attentionState == ATTENTION_BLINK_TIMEOUT ||
         attentionState % 2 == 0))
    {
        // blink until blink timeout, then display differently without blinking
        colors.setColor( QColorGroup::Button,     colors.highlight() );
        colors.setColor( QColorGroup::Background, colors.highlight() );
        colors.setColor( QColorGroup::ButtonText, colors.highlightedText() );
        colors.setColor( QColorGroup::Text,       colors.highlightedText() );
    }
    if (active || aboutToActivate)
        colors.setColor(QColorGroup::Button, colors.button().dark(110));

    // get the task icon
    if ( task )
	pixmap = task->pixmap();
    else // we must be a startup (and therefor can't be minimized)
	iconified = false;

    bool sunken = isDown() || active || aboutToActivate;
    bool reverse = QApplication::reverseLayout();
    QRect br( style().subRect( QStyle::SR_PushButtonContents, this ) );
    QPoint shift = QPoint( style().pixelMetric(QStyle::PM_ButtonShiftHorizontal),
			style().pixelMetric(QStyle::PM_ButtonShiftVertical) );

    // draw button background
    style().drawPrimitive( QStyle::PE_HeaderSection, p, QRect( 0, 0, width(), height() ),
                           colors, sunken ? QStyle::Style_Down : QStyle::Style_Raised );

    // shift button label on sunken buttons
    if ( sunken )
	p->translate( shift.x(), shift.y() );

    if ( taskBar->showIcon() ) {
        if ( pixmap.isNull() && !startups.isEmpty() )
	    pixmap = SmallIcon( startups.first()->icon() );

	if ( !pixmap.isNull() ) {

	    // make sure it is no larger than 16x16
	    if ( pixmap.width() > 16 || pixmap.height() > 16 ) {
	        QImage tmp = pixmap.convertToImage();
		pixmap.convertFromImage( tmp.smoothScale( 16, 16 ) );
	    }

	    // fade out the icon when minimized
	    // only do this if we are not showing only iconified windows
	    // it looks pretty stupid when they are *all* faded :-)
            if (!TaskBarSettings::showOnlyIconified() && iconified)
		KIconEffect::semiTransparent( pixmap );

	    // draw icon
	    QRect pmr( 0, 0, pixmap.width(), pixmap.height() );
	    pmr.moveCenter( iconRect.center() );
	    p->drawPixmap( pmr, pixmap );
	}
    }

    // find text
    QString text = name();

    // modified overlay
    static QString modStr = "[" + i18n( "modified" ) + "]";
    int modStrPos = text.find( modStr );
    int textPos = ( taskBar->showIcon() && !pixmap.isNull() ) ? 2 + 16 + 2 : 0;

    if ( modStrPos >= 0 ) {

	// +1 because we include a space after the closing brace.
	text.remove( modStrPos, modStr.length() + 1 );

	QPixmap modPixmap = SmallIcon( "modified" );

	// draw modified overlay
	if ( ! modPixmap.isNull() ) {
	    QRect r = QStyle::visualRect( QRect( br.x() + textPos, (height() - 16) / 2, 16, 16 ), this );

            if ( !TaskBarSettings::showOnlyIconified() && iconified )
	    	KIconEffect::semiTransparent( modPixmap );

	    p->drawPixmap( r, modPixmap );
	    textPos += 16 + 2;
	}
    }

    // draw text
    if ( !text.isEmpty() ) {

	QRect tr = QStyle::visualRect( QRect( br.x() + textPos + 1, 0, width() - textPos, height() ), this );
	int textFlags = AlignVCenter | SingleLine;
	textFlags |= reverse ? AlignRight : AlignLeft;
	QPen textPen;

	// get the color for the text label
        if ( !TaskBarSettings::showOnlyIconified() && iconified )
	    textPen = QPen( blendColors(colors.button(), colors.buttonText()) );
	else if ( ! active )
	    textPen = QPen( colors.buttonText() );
	else // hack for the dotNET style and others
	    textPen = p->pen();

        int availableWidth = width() - (br.x() * 2) - textPos;
        if ( m_filteredTasks.count() > 1 )
        {
            availableWidth -= 8;
        }

        if ( QFontMetrics( font ).width( text ) > availableWidth )
        {

	    if ( blendGradient.isNull() || blendGradient.size() != size() ) {

		QPixmap bgpm( size() );
		QPainter bgp( &bgpm );
		bgpm.fill( black );

		if ( ! reverse ) {
		    QImage gradient = KImageEffect::gradient( QSize( 30, height() ), QColor( 0,0,0 ),
							QColor( 255,255,255 ), KImageEffect::HorizontalGradient );
		    bgp.drawImage( width() - 30, 0, gradient );
		} else {
		    QImage gradient = KImageEffect::gradient( QSize( 30, height() ), QColor( 255,255,255 ),
							QColor( 0,0,0 ), KImageEffect::HorizontalGradient );
		    bgp.drawImage( 0, 0, gradient );
		}

		blendGradient = bgpm.convertToImage();
	    }

	    // draw text into overlay pixmap
	    QPixmap tpm( *pm );
	    QPainter tp( &tpm );

	    // shift button label on sunken buttons
	    // (have to do it again because it's a different painter)
	    if ( sunken )
		tp.translate( shift.x(), shift.y() );

	    tp.setFont( font );
	    tp.setPen( textPen );

	    tp.drawText( tr, textFlags, text );

	    // blend text into background image
	    QImage img = pm->convertToImage();
	    QImage timg = tpm.convertToImage();
	    KImageEffect::blend( img, timg, blendGradient, KImageEffect::Red );

	    pm->convertFromImage( img );
	}
	else {
	    p->setFont( font );
   	    p->setPen( textPen );
	    p->drawText( tr, textFlags, text );
	}
    }

    if( frames && !startups.isEmpty())
    {
        QPixmap *anim = frames->at( currentFrame );
        if ( anim && !anim->isNull() ) {
            // save the background for the other frames
            bitBlt( &animBg, QPoint(0,0), pm, iconRect );
            // draw the animation frame
            bitBlt( pm, iconRect.x(), iconRect.y(), anim );
        }
    }

    // draw popup arrow
    if ( m_filteredTasks.count() > 1 )
    {
        QStyle::PrimitiveElement e = QStyle::PE_ArrowLeft;

        switch ( arrowType )
        {
            case Qt::LeftArrow:  e = QStyle::PE_ArrowLeft;  break;
            case Qt::RightArrow: e = QStyle::PE_ArrowRight; break;
            case Qt::UpArrow:    e = QStyle::PE_ArrowUp;    break;
            case Qt::DownArrow:  e = QStyle::PE_ArrowDown;  break;
        }
        int flags = QStyle::Style_Enabled;
        QRect ar = QStyle::visualRect( QRect( br.x() + br.width() - 8 - 2, br.y(), 8, br.height() ), this );
        if ( sunken ) {
            flags |= QStyle::Style_Down;
            // Change the painter back so the arrow gets drawn in the right location
            p->translate( -shift.x(), -shift.y() );
        }

        style().drawPrimitive( e, p, ar, colors, flags );
    }

    if ( aboutToActivate )
        aboutToActivate = false;
}

QString TaskContainer::name()
{
    // default to container id
    QString text;

    // single task -> use mainwindow caption
    if (m_filteredTasks.count() == 1)
    {
        text = m_filteredTasks.first()->visibleIconicName();
    }
    else if (m_filteredTasks.count() > 1)
    {
        // multiple tasks -> use the common part of all captions
        // if it is more descriptive than the class name
        const QString match = m_filteredTasks.first()->visibleIconicName();
        unsigned int maxLength = match.length();
        unsigned int i = 0;
        bool stop = false;

        // what we do is find the right-most letter than the names do NOT have
        // in common, and then use everything UP TO that as the name in the button
        while (i < maxLength)
        {
            QChar check = match.at(i).lower();
            TaskList::iterator itEnd = m_filteredTasks.end();
            TaskList::iterator it = m_filteredTasks.begin();
            for (++it; it != itEnd; ++it)
            {
                Task::Ptr t = *it;
                // we're doing a lot of Utf8 -> QString conversions here
                // by repeatedly calling visibleIconicName() =/
                if (check != t->visibleIconicName().at(i).lower())
                {
                    if (i > 0)
                    {
                        --i;
                    }
                    stop = true;
                    break;
                }
            }

            if (stop)
            {
                break;
            }

            ++i;
        }

        // strip trailing crap
        while (i > 0 && !match.at(i).isLetterOrNumber())
        {
            --i;
        }

        // more descriptive than id()?
        if (i > 0 && (i + 1) >= id().length())
        {
            text = match.left(i + 1);
        }
    }
    else
    {
        // fall back to startup name
        StartupList::iterator itEnd = startups.end();
        for (StartupList::iterator it = startups.begin(); it != itEnd; ++it)
        {
            Startup::Ptr s = *it;
            if (!s->text().isEmpty())
            {
                text = s->text();
                break;
            }
        }
    }

    if (text.isEmpty())
    {
        text = id();

        // Upper case first letter: seems to be the right thing to do for most cases
        text[0] = text[0].upper();
    }

    if (m_filteredTasks.count() > 1)
    {
        // this is faster than (" [%1]").arg() or +
        // and it's as fast as using append, but cleaner looking
        text += " [";
        text += QString::number(m_filteredTasks.count());
        text += "]";
    }

    return text;
}

void TaskContainer::mousePressEvent( QMouseEvent* e )
{
    if( discardNextMouseEvent )
    {
        discardNextMouseEvent = false;
        return;
    }

    // On left button, only do actions that invoke a menu.
    // Other actions will be handled in slotClicked().
    if (e->button() == LeftButton &&
        ((leftButtonAction == TaskBar::ShowTaskList && m_filteredTasks.count() > 1) ||
        leftButtonAction == TaskBar::ShowOperationsMenu))
    {
        performAction(leftButtonAction);
    }
    else if (e->button() == MidButton)
    {
        performAction(middleButtonAction);
    }
    else if (e->button() == RightButton)
    {
        performAction(rightButtonAction);
    }
    else
    {
        QToolButton::mousePressEvent(e);
    }
}

void TaskContainer::mouseReleaseEvent(QMouseEvent *e)
{
    // This is to avoid the flicker caused by redrawing the
    // button as unpressed just before it's activated.
    if (rect().contains(e->pos()))
    {
        if (e->button() == LeftButton)
        {
            if (leftButtonAction == TaskBar::ActivateRaiseOrIconify ||
                leftButtonAction == TaskBar::Activate)
            {
                aboutToActivate = true;
            }
        }
        else if (e->button() == MidButton)
        {
            if (middleButtonAction == TaskBar::ActivateRaiseOrIconify ||
                middleButtonAction == TaskBar::Activate)
            {
                aboutToActivate = true;
            }
        }
        else if (e->button() == RightButton)
        {
            if (rightButtonAction == TaskBar::ActivateRaiseOrIconify ||
                rightButtonAction == TaskBar::Activate)
            {
                aboutToActivate = true;
            }
        }
    }

    QToolButton::mouseReleaseEvent(e);
    QTimer::singleShot(75, this, SLOT(update()));
}

void TaskContainer::slotClicked()
{
    // We've already handled these cases above by
    // showing the menu.
    if ((leftButtonAction == TaskBar::ShowTaskList &&
         m_filteredTasks.count() > 1) ||
        leftButtonAction == TaskBar::ShowOperationsMenu)
    {
        return;
    }

    performAction( leftButtonAction );
}

void TaskContainer::performAction( TaskBar::Action action )
{
    if ( m_filteredTasks.isEmpty() )
        return;

    switch( action ) {
    case TaskBar::ShowTaskList:
	// If there is only one task, the correct behavior is
	// to activate, raise, or iconify it, not show the task menu.
	if( m_filteredTasks.count() > 1 ) {
	    popupMenu( TaskBar::ShowTaskList );
	} else {
	    performAction( TaskBar::ActivateRaiseOrIconify );
	}
	break;
    case TaskBar::ShowOperationsMenu:
	popupMenu( TaskBar::ShowOperationsMenu );
	break;
    case TaskBar::ActivateRaiseOrIconify:
        if ( m_filteredTasks.count() == 1)
        {
            m_filteredTasks.first()->activateRaiseOrIconify();
        }
        else
        {
            // multiple tasks -> cycle list
            TaskList::iterator itEnd = m_filteredTasks.end();
            for (TaskList::iterator it = m_filteredTasks.begin(); it != itEnd; ++it)
            {
                if ((*it)->isActive())
                {
                    // activate next
                    ++it;
                    if (it == itEnd)
                    {
                        it = m_filteredTasks.begin();
                    }
                    (*it)->activateRaiseOrIconify();
                    return;
                }
            }

            if (m_filteredTasks.find(lastActivated) != m_filteredTasks.end())
            {
                lastActivated->activateRaiseOrIconify();
            }
            else
            {
                m_filteredTasks.first()->activateRaiseOrIconify();
            }
        }
        break;
    case TaskBar::Activate:
	m_filteredTasks.first()->activate();
	break;
    case TaskBar::Raise:
	m_filteredTasks.first()->raise();
	break;
    case TaskBar::Lower:
	m_filteredTasks.first()->lower();
	break;
    case TaskBar::Iconify:
        m_filteredTasks.first()->toggleIconified();
        break;
    default:
	kdWarning(1210) << "Unknown taskbar action!" << endl;
	break;
    }
}

// forcenext == true means the last entry in the previous
// taskcontainer was active -> activate first
bool TaskContainer::activateNextTask(bool forward, bool& forcenext)
{
    if (forcenext)
    {
        if (m_filteredTasks.isEmpty())
        {
            return false;
        }

        if (forward)
        {
            m_filteredTasks.first()->activate();
        }
        else
        {
            m_filteredTasks.last()->activate();
        }

        forcenext = false;
        return true;
    }

    TaskList::iterator itEnd = m_filteredTasks.end();
    for (TaskList::iterator it = m_filteredTasks.begin(); it != itEnd; ++it)
    {
        if ((*it)->isActive())
        {
            if (forward)
            {
                ++it;
                if (it == itEnd)
                {
                    forcenext = true;
                    return false;
                }

                (*it)->activate();
                return true;
            }
            else if (it == m_filteredTasks.begin())
            {
                forcenext = true;
                return false;
            }

            --it;
            (*it)->activate();
            return true;
        }
    }

    return false;
}

void TaskContainer::popupMenu(TaskBar::Action action)
{
    if( action == TaskBar::ShowTaskList )
    {
        m_menu = new TaskLMBMenu( &m_filteredTasks );
    }
    else if( action == TaskBar::ShowOperationsMenu )
    {
        if (!kapp->authorizeKAction("kwin_rmb"))
        {
            return;
        }

        m_menu = new TaskRMBMenu(&m_filteredTasks, taskBar->showAllWindows());
    }
    else
    {
        return;
    }

    // calc popup menu position
    QPoint pos(mapToGlobal(QPoint(0, 0)));

    switch( arrowType )
    {
        case RightArrow:
            pos.setX(pos.x() + width());
            break;
        case LeftArrow:
            pos.setX(pos.x() - m_menu->sizeHint().width());
            break;
        case DownArrow:
            if ( QApplication::reverseLayout() )
                pos.setX( pos.x() + width() - m_menu->sizeHint().width() );
            pos.setY( pos.y() + height() );
            break;
        case UpArrow:
            if ( QApplication::reverseLayout() )
                pos.setX( pos.x() + width() - m_menu->sizeHint().width() );
            pos.setY(pos.y() - m_menu->sizeHint().height());
            break;
        default:
            break;
    }
    m_menu->installEventFilter( this );
    m_menu->exec( pos );

    delete m_menu;
    m_menu = 0;
}

// This is the code that gives us the proper behavior
// when a popup menu is displayed and we are clicked:
// close the menu, and don't reopen it immediately.
// It's copied from QToolButton. Unfortunately Qt is lame
// as usual and makes interesting stuff private or
// non-virtual, so we have to copy code.
bool TaskContainer::eventFilter( QObject *o, QEvent *e )
{
    switch ( e->type() )
    {
        case QEvent::MouseButtonPress:
        case QEvent::MouseButtonDblClick:
        {
            QMouseEvent *me = (QMouseEvent*)e;
            QPoint p = me->globalPos();
            if ( QApplication::widgetAt( p, true ) == this )
            discardNextMouseEvent = true;
            break;
        }
        default:
            break;
    }
    return QToolButton::eventFilter( o, e );
}

void TaskContainer::setArrowType( Qt::ArrowType at )
{
    if (arrowType == at)
    {
        return;
    }

    arrowType = at;
    repaint();
}

void TaskContainer::publishIconGeometry( QPoint global )
{
    QPoint p = global + geometry().topLeft();

    TaskList::iterator itEnd = m_filteredTasks.end();
    for (TaskList::iterator it = m_filteredTasks.begin(); it != itEnd; ++it)
    {
        (*it)->publishIconGeometry( QRect( p.x(), p.y(), width(), height() ) );
    }
}

void TaskContainer::dragEnterEvent( QDragEnterEvent* e )
{
    // if a dragitem is held for over a taskbutton for two seconds,
    // activate corresponding window

    if ( m_filteredTasks.count() < 1 ) return;

    if( !m_filteredTasks.first()->isActive() || m_filteredTasks.count() > 1 )
        dragSwitchTimer.start( 1000, true );

    QToolButton::dragEnterEvent( e );
}

void TaskContainer::dragLeaveEvent( QDragLeaveEvent* e )
{
    dragSwitchTimer.stop();

    QToolButton::dragLeaveEvent( e );
}

void TaskContainer::dragSwitch()
{
    if ( m_filteredTasks.count() < 1 )
        return;
    else if ( m_filteredTasks.count() == 1 )
        m_filteredTasks.first()->activate();
    else
        popupMenu( TaskBar::ShowTaskList );
}

int TaskContainer::desktop()
{
    if ( tasks.isEmpty() )
        return TaskManager::the()->currentDesktop();

    if ( tasks.count() > 1 )
        return TaskManager::the()->numberOfDesktops();

    return tasks.first()->desktop();
}

bool TaskContainer::onCurrentDesktop()
{
    if ( isEmpty() )
    {
        return false;
    }

    if ( tasks.count() < 1 &&
         startups.count() > 0 )
    {
        return true;
    }

    for ( TaskList::iterator it = tasks.begin(); it != tasks.end() ; ++it )
    {
        if ( (*it)->isOnCurrentDesktop() )
        {
            return true;
        }
    }

    return false;
}

bool TaskContainer::isOnScreen()
{
    if ( isEmpty() )
    {
        return false;
    }

    int screen = taskBar->showScreen();
    if ( ( tasks.count() < 1 && startups.count() > 0 ) || screen == -1 )
    {
        return true;
    }

    TaskList::iterator itLast = tasks.end();
    for ( TaskList::iterator it = tasks.begin(); it != itLast; ++it )
    {
        if ( (*it)->isOnScreen( screen ) )
        {
            return true;
        }
    }

    return false;
}

bool TaskContainer::isIconified()
{
    if (isEmpty())
    {
        return false;
    }

    if (tasks.count() < 1 && startups.count() > 0)
    {
        return true;
    }

    for ( TaskList::iterator it = tasks.begin(); it != tasks.end() ; ++it )
    {
       if ((*it)->isIconified())
       {
            return true;
       }
    }

    return false;
}

void TaskContainer::updateFilteredTaskList()
{
    m_filteredTasks.clear(); // m_filteredTasks == filtered tasks

    for ( TaskList::iterator it = tasks.begin(); it != tasks.end() ; ++it )
    {
        Task::Ptr t = *it;
        if ((taskBar->showAllWindows() || t->isOnCurrentDesktop()) &&
            (!TaskBarSettings::showOnlyIconified() || t->isIconified()))
        {
            m_filteredTasks.append( t );
        }
    }

    // sort container list by desktop
    if ( taskBar->sortByDesktop() && m_filteredTasks.count() > 1 )
    {
        TaskList sorted;
        Task::Ptr t;
        /* antlarr: residue shouldn't be needed, as "in theory" we already
        iterate through all applications, but kicker is a core app and we
        don't want it to crash nor hang under any circumstance in the real
        world, so in case a window has been moved to an out-of-range desktop
        (which may be possible just by calling NETWinInfo::setDesktop(42) )
        we want to keep sanity */
        TaskList residue = m_filteredTasks;
        int numDesktops = TaskManager::the()->numberOfDesktops();
        for ( int desktop = -1; desktop <= numDesktops; desktop++ )
        {
            TaskList::iterator itEnd = m_filteredTasks.end();
            for (TaskList::iterator it = m_filteredTasks.begin(); it != itEnd; ++it)
            {
                Task::Ptr t = *it;
                if ( t->desktop() == desktop )
                {
                    sorted.append( t );
                    residue.remove( t );
                }
            }
        }
        m_filteredTasks = sorted;

        TaskList::iterator itEnd = residue.end();
        for (TaskList::iterator it = residue.begin(); it != itEnd; ++it)
        {
            m_filteredTasks.append( *it );
        }
    }
}

void TaskContainer::desktopChanged( int )
{
    updateFilteredTaskList();
    update();
}

void TaskContainer::windowChanged( WId )
{
    updateFilteredTaskList();
    update();
}

void TaskContainer::settingsChanged()
{
    updateFilteredTaskList();
    update();
}
