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

Copyright (c) 2000 Matthias Elter <elter@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 <qpopupmenu.h>
#include <qtooltip.h>
#include <qtimer.h>
#include <qpainter.h>
#include <qlayout.h>

#include <kconfig.h>
#include <kstyle.h>
#include <klocale.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <kdebug.h>
#include <kstddirs.h>
#include <kprocess.h>
#include <kapp.h>
#include <kstyle.h>

#include "taskbarextension.h"
#include "taskbarextension.moc"
#include "taskmanager.h"

extern "C"
{
    KPanelExtension* init(QWidget *parent, const QString& configFile)
    {
   	return new TaskBarExtension(configFile, KPanelExtension::Stretch,
				    KPanelExtension::Preferences, parent, "taskbarextension");
    }
}

TaskManager* task_manager = 0;

TaskBarExtension::TaskBarExtension(const QString& configFile, Type type,
				   int actions, QWidget *parent, const char *name)
    : KPanelExtension(configFile, type, actions, parent, name), _layout(0), _showall(false)
{
    // read settings
    KConfig c("ktaskbarrc", false, false);
    c.setGroup("General");
    _showall = c.readBoolEntry("ShowAllWindows", false);

    // set font
    QFont f = c.readFontEntry("taskbarFont");
    setFont(f);

    // setup and connect manager
    task_manager = new TaskManager(this);
    connect(task_manager, SIGNAL(taskAdded(Task*)), SLOT(taskAdded(Task*)));
    connect(task_manager, SIGNAL(taskRemoved(Task*)), SLOT(taskRemoved(Task*)));
    connect(task_manager, SIGNAL(startupAdded(Startup*)), SLOT(startupAdded(Startup*)));
    connect(task_manager, SIGNAL(startupRemoved(Startup*)), SLOT(startupRemoved(Startup*)));
    connect(task_manager, SIGNAL(desktopChanged(int)), SLOT(desktopChanged(int)));

    // register existant tasks
    QList<Task> tasks = task_manager->tasks();

    for (Task* t = tasks.first(); t!=0; t = tasks.next()) {
        TaskButton* b = new TaskButton(t, this);
        _taskButtons.append(b);
    }

    // register existant startups
    QList<Startup> startups = task_manager->startups();

    for (Startup* s = startups.first(); s!=0; s = startups.next()) {
        StartupButton* b = new StartupButton(s, this);
        _startupButtons.append(b);
    }

    // publish timer
    connect(&_publishTimer, SIGNAL(timeout()), this, SLOT(publishIconGeometry()));
}

TaskBarExtension::~TaskBarExtension()
{
}

void TaskBarExtension::taskAdded(Task* task)
{
    if(!task) return;

    TaskButton* b = new TaskButton(task, this);
    _taskButtons.append(b);
    emit updateLayout();
    resizeEvent(0L);
}

void TaskBarExtension::taskRemoved(Task* task)
{
    if(!task) return;

    TaskButton* b;
    for (b = _taskButtons.first(); b != 0; b = _taskButtons.next()) {
        if (b->task() == task)
            break;
    }

    if (!b) return;

    _taskButtons.removeRef(b);
    delete(b);
    emit updateLayout();
    resizeEvent(0L);
}

void TaskBarExtension::startupAdded(Startup* startup)
{
    if(!startup) return;

    StartupButton* b = new StartupButton(startup, this);
    _startupButtons.append(b);
    emit updateLayout();
    resizeEvent(0L);
}

void TaskBarExtension::startupRemoved(Startup* startup)
{
    if(!startup) return;

    StartupButton* b;
    for (b = _startupButtons.first(); b != 0; b = _startupButtons.next()) {
        if (b->startup() == startup)
            break;
    }

    if (!b) return;

    _startupButtons.removeRef(b);
    delete(b);
    emit updateLayout();
    resizeEvent(0L);
}

void TaskBarExtension::desktopChanged(int)
{
    if(!_showall)
        resizeEvent(0L);
}

void TaskBarExtension::resizeEvent(QResizeEvent*)
{
    if(_layout) {
        delete _layout;
	_layout = 0;
    }

    bool horiz = orientation() == Horizontal;
    int items, i, col, row, mod = 0;

    TaskButton *b;
    for(items=0, b = _taskButtons.first(); b; b = _taskButtons.next())
        items += (b->task()->onCurrentDesktop() || _showall)?1:0;

    items += _startupButtons.count();

    if(horiz) {
        if(height() < 25)
            mod = 1;
        else if (height() < 49)
            mod = 2;
        else
            mod = 3;
    }
    else
        mod = 1;

    _layout = new QGridLayout(this, mod ,1 );

    for(i=0, col=0, row=0, b = _taskButtons.first(); b; b = _taskButtons.next()){

        if(b->task()->onCurrentDesktop() || _showall){
            b->show();
            if(horiz)
                _layout->addWidget(b, row, col);
            else
                _layout->addWidget(b, row, 0);
            ++i;
            if ( horiz && ((i%mod) == 0) ) {
		row = 0;
		++col;
            }
            else
		++row;
        }
        else{
            b->move(0, 0);
            b->hide();
        }
    }

    for (StartupButton* b = _startupButtons.first(); b!=0; b = _startupButtons.next()) {

        b->show();

 	if (horiz)
 	    _layout->addWidget(b, row, col);
        else
 	    _layout->addWidget(b, row, 0);

 	++i;
 	if ( horiz && ((i%mod) == 0) ) {
 	    row = 0;
 	    ++col;
 	}
 	else
 	    ++row;
    }

    if(!_layout) return;

    if(horiz)
    	_layout->setColStretch(++col, 1);
    else
    	_layout->setRowStretch(++row, 1);
    _layout->activate();
    updateGeometry();
    _publishTimer.start(100, true);
}

QSize TaskBarExtension::sizeHint(Position p, QSize maxSize) const
{
    int v = 0;

    QListIterator<TaskButton> it(_taskButtons);
    for ( ; it.current(); ++it )
        v += ((*it)->task()->onCurrentDesktop() || _showall)?1:0;

    v += _startupButtons.count();

    if (p == Left || p == Right)
	return QSize(100, 24 * v);
    else {
	if (v > 40)
	    return QSize(maxSize.width(), 72);
        else if (v > 20)
	    return QSize(maxSize.width(), 48);
	else
	    return QSize(maxSize.width(), 24);
    }
}

void TaskBarExtension::publishIconGeometry()
{
    QPoint p = mapToGlobal( QPoint(0,0) ); // roundtrip, don't do that too often
    for (TaskButton* b = _taskButtons.first(); b != 0; b = _taskButtons.next())
        b->publishIconGeometry( p );
}

void TaskBarExtension::mousePressEvent(QMouseEvent* e)
{
    propagateMouseEvent(e);
}
void TaskBarExtension::mouseReleaseEvent(QMouseEvent* e)
{
    propagateMouseEvent(e);
}
void TaskBarExtension::mouseDoubleClickEvent(QMouseEvent* e)
{
    propagateMouseEvent(e);
}
void TaskBarExtension::mouseMoveEvent(QMouseEvent* e)
{
    propagateMouseEvent(e);
}

void TaskBarExtension::propagateMouseEvent(QMouseEvent* e)
{
    if ( !isTopLevel()  ) {
	QMouseEvent me(e->type(), mapTo( topLevelWidget(), e->pos() ),
		       e->globalPos(), e->button(), e->state() );
	QApplication::sendEvent( topLevelWidget(), &me );
    }
}

void TaskBarExtension::preferences()
{
    KProcess proc;
    proc << locate("exe", "kcmshell");
    proc << "LookNFeel/kcmtaskbar";
    proc.start(KProcess::DontCare);
}

TaskButton::TaskButton( Task* task, QWidget* parent, const char *name )
    : QPushButton(parent, name), _task(task), _popup(0), _deskpopup(0)
{
    setToggleButton(true);
    setAcceptDrops(true);
    setBackgroundMode(NoBackground);
    setMinimumWidth(50);

    connect(this, SIGNAL(clicked()), SLOT(toggled()));
    connect(_task, SIGNAL(changed()), SLOT(refresh()));

    // drag switch timer
    _dragSwitchTimer = new QTimer(this);
    connect(_dragSwitchTimer, SIGNAL(timeout()), SLOT(dragSwitchTimeout()));

    connect(this, SIGNAL(clicked()), SLOT(toggled()));

    refresh();
}

void TaskButton::dragEnterEvent(QDragEnterEvent*)
{
    // if a dragitem is held for over a taskbutton for two seconds,
    // activate corresponding window
    if(!_task->active())
	_dragSwitchTimer->start(2000, TRUE);
}

void TaskButton::dragLeaveEvent(QDragLeaveEvent*)
{
    _dragSwitchTimer->stop();
}

void TaskButton::dragSwitchTimeout()
{
    _task->activate();
}

void TaskButton::refresh()
{
    QString name = _task->visibleName();

    if(name != text()) {
        setText(name);
        QToolTip::add(this, name);
    }
    setOn(_task->active());
    update();
}

QSize TaskButton::sizeHint() const
{
    QPainter p (this);
    int width = 28 + p.fontMetrics().width(text());

    return QSize(width, 24);
}

void TaskButton::drawButton(QPainter *p)
{
    int pxWidth = 20;
    int textPos = 0;
    const int margin = 4;
    bool sunken = isOn(); // || isDown();

    if(kapp->kstyle()){
        kapp->kstyle()->drawKickerTaskButton(p, 0, 0, width(), height(),
                                             colorGroup(), QString::null,
                                             sunken, NULL);
    }
    else{
        if (sunken)
            style().drawPanel(p, 0, 0, width(), height(), colorGroup(), true, 1,
                              &colorGroup().brush(QColorGroup::Highlight));
        else
            p->fillRect(rect(), colorGroup().brush(QColorGroup::Background));
    }
    // draw icon
    QPixmap pixmap = _task->pixmap();

    if (!pixmap.isNull()) {

        int dx = (pxWidth   - pixmap.width())   / 2;
        int dy = (height()  - pixmap.height())  / 2;

        p->drawPixmap(margin + dx, dy, pixmap);
        textPos += pxWidth;
    }

    QString s(text());

    // modified icon
    static QString modStr = QString::fromUtf8("[") + i18n("modified") + QString::fromUtf8("]");
    int modStrPos = s.find(modStr);

    if (-1 != modStrPos) {

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

        QPixmap modPixmap = SmallIcon("modified");

        int dx = (pxWidth   - modPixmap.width())  / 2;
        int dy = (height()  - modPixmap.height()) / 2;

        p->drawPixmap(margin + textPos + dx, dy, modPixmap);
        textPos += pxWidth;
    }

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

        if (p->fontMetrics().width(s) > width() - margin *2 - textPos) {

            int maxLen = width() -margin * 2 - textPos - p->fontMetrics().width("...");
            while ((!s.isEmpty()) && (p->fontMetrics().width(s) > maxLen))
                s.truncate(s.length() - 1);

            s.append("...");
        }

        if (sunken)
            p->setPen(colorGroup().highlightedText());
        else if (_task->iconified())
            p->setPen(colorGroup().dark());
        else
            p->setPen(colorGroup().text());

        p->drawText(margin + textPos, -1, width() - textPos, height(), AlignLeft | AlignVCenter, s);
    }
}

void TaskButton::toggled()
{
    if (isOn())
        _task->activate();
    else if (!task_manager->isOnTop(_task))
        _task->raise();
    else
	_task->iconify();
}

void TaskButton::clientPopupAboutToShow()
{
    if (!_popup) return;

    _popup->setItemEnabled(RestoreOp, _task->iconified() || _task->maximized());
    _popup->setItemEnabled(IconifyOp, !_task->iconified());
    _popup->setItemEnabled(MaximizeOp, !_task->maximized());
    _popup->setItemEnabled(ToCurrentOp, !_task->onCurrentDesktop());
    _popup->setItemChecked(StayOnTopOp, _task->staysOnTop());
    _popup->setItemChecked(ShadeOp, _task->shaded());
}

void TaskButton::desktopPopupAboutToShow()
{
    if (!_deskpopup) return;

    _deskpopup->clear();
    _deskpopup->insertItem( i18n("&All Desktops"), 0 );
    _deskpopup->insertSeparator();

    if (_task->onAllDesktops())
        _deskpopup->setItemChecked(0, true);

    int id;
    for (int i = 1; i <= task_manager->numberOfDesktops(); i++) {
        id = _deskpopup->insertItem( QString("&%1 %2").arg(i).arg(task_manager->desktopName(i)), i );
        if ( !_task->onAllDesktops() && _task->desktop() == i )
            _deskpopup->setItemChecked( id, TRUE );
    }
}

void TaskButton::clientPopupActivated( int id )
{
    switch (id) {
        case MaximizeOp:
            _task->maximize();
            break;
        case IconifyOp:
            _task->iconify();
            break;
        case RestoreOp:
            _task->restore();
            break;
        case CloseOp:
            _task->close();
            break;
        case ToCurrentOp:
            _task->toCurrentDesktop();
            break;
        case StayOnTopOp:
            _task->stayOnTop(!_task->staysOnTop());
            break;
        case ShadeOp:
            _task->shade(!_task->shaded());
            break;
        default:
            break;
    }
}

void TaskButton::publishIconGeometry(const QPoint& global)
{
    QPoint p = global + geometry().topLeft();
    _task->publishIconGeometry(QRect(p.x(), p.y(), width(), height()));
}

void TaskButton::sendToDesktop(int desk)
{
    _task->toDesktop(desk);
}

void TaskButton::mousePressEvent(QMouseEvent *e)
{
    if ( e->button() == QMouseEvent::RightButton )
    {
        if (!_popup)
        {
            _popup = new QPopupMenu;
            _popup->setCheckable(true );
            connect(_popup, SIGNAL(aboutToShow()), this, SLOT(clientPopupAboutToShow()));
            connect(_popup, SIGNAL(activated(int)), this, SLOT(clientPopupActivated(int)));

            if (!_deskpopup) _deskpopup = new QPopupMenu(_popup);
            _deskpopup->setCheckable(true);
            connect(_deskpopup, SIGNAL(aboutToShow()), this, SLOT(desktopPopupAboutToShow()));
            connect(_deskpopup, SIGNAL(activated(int)), this, SLOT(sendToDesktop(int)));

            _popup->insertItem(i18n("Mi&nimize"), IconifyOp);
            _popup->insertItem(i18n("Ma&ximize"), MaximizeOp);
            _popup->insertItem(i18n("&Restore"), RestoreOp);

            _popup->insertSeparator();

            _popup->insertItem(i18n("&Shade"), ShadeOp);
            _popup->insertItem(SmallIcon("attach"), i18n("&Always On Top"), StayOnTopOp);

            _popup->insertSeparator();

            _popup->insertItem(SmallIcon("remove"), i18n("&Close"), CloseOp);

            _popup->insertSeparator();

            _popup->insertItem(i18n("To &Desktop"), _deskpopup);
            _popup->insertItem(i18n("&To Current Desktop"), ToCurrentOp);

        }
        _popup->popup(e->globalPos());
        return;
    }
    QPushButton::mousePressEvent(e);
}

StartupButton::StartupButton(Startup* startup, QWidget* parent, const char *name)
    : QPushButton(parent, name), _startup(startup), _frame(0)
{
    setBackgroundMode(NoBackground);
    setMinimumWidth(50);
    setText(_startup->text());
    QToolTip::add(this, i18n("Starting %1").arg(_startup->text()));

    // setup animation frames
    _anim.setAutoDelete(true);
    for (int i = 1; i < 9; i++)
	_anim.append(new QPixmap(locate("data", "kicker/pics/disk" + QString::number(i) + ".png")));

    // setup animation timer
    connect(&_animTimer, SIGNAL(timeout()), this, SLOT(animTimerFired()));
    _animTimer.start(100);
}

void StartupButton::animTimerFired()
{
    QPainter p(this);
    QPixmap *pix = _anim.at(_frame);

    p.drawPixmap(4, (height() - pix->height())/2, *pix);

    if (_frame == 7)
	_frame = 0;
    else
	_frame++;
}

QSize StartupButton::sizeHint() const
{
    QPainter p (this);
    int width = 28 + p.fontMetrics().width(text());

    return QSize(width, 24);
}

void StartupButton::drawButton(QPainter * p)
{
    const int pxWidth = 20;
    const int margin = 4;
    bool sunken = isOn(); // || isDown();

    if(kapp->kstyle()){
        kapp->kstyle()->drawKickerTaskButton(p, 0, 0, width(), height(),
                                             colorGroup(), QString::null,
                                             sunken, NULL);
    }
    else
        p->fillRect(rect(), colorGroup().brush(QColorGroup::Background));

    // draw text
    QString s(text());
    if (!s.isEmpty()){
	if (p->fontMetrics().width(s) > width() - margin * 2 - pxWidth) {

	    int maxLen = width() - margin * 2 - pxWidth - p->fontMetrics().width("...");

	    while ((!s.isEmpty()) && (p->fontMetrics().width(s) > maxLen))
		s.truncate(s.length() - 1);

	    s.append("...");
	}

        p->setPen(sunken ? colorGroup().highlightedText() : colorGroup().foreground());
	p->drawText(margin + pxWidth, 0, width() - pxWidth, height(), AlignLeft | AlignVCenter, s);
    }
}

void StartupButton::mousePressEvent(QMouseEvent *)
{
    //Don't call default handler ... we literally want to do nothing!
}
