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

Copyright (c) 1996-2000 the kicker authors. See file AUTHORS.

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 <qtooltip.h>
#include <qdrawutil.h>
#include <qlayout.h>
#include <kwin.h>
#include <kwm.h>
#include <kwinmodule.h>
#include <kapp.h>
#include <kstyle.h>
#include <kglobal.h>
#include <klocale.h>
#include <kdebug.h>
#include <kiconloader.h>
#include <X11/Xutil.h>
// kdesktopfile.h includes QVariant, which defines Bool and clashes with X.
#undef Bool
#include <kdesktopfile.h>
#include <kservice.h>
#include <kdrawutil.h>
#include <qpopupmenu.h>
#include <qvaluelist.h>

#include "taskbar.h"

template class QList<TaskButton>;
template class QList<AppStartButton>;

KWinModule* kwin_module;

extern "C"
{
    InternalApplet* init(QWidget *parent, KWinModule *module)
    {
        KConfig *config = KGlobal::config();
        KConfigGroupSaver saver(config, "taskbar");
        kwin_module = module;
        KGlobal::locale()->insertCatalogue("ktaskbarapplet");
        if (config->readBoolEntry("TopLevel", false))
            return new TaskbarApplet("ktaskbarapplet");
        else
            return new TaskbarApplet(parent, "ktaskbarapplet");
    }
}

TaskbarApplet::TaskbarApplet( const char* name )
    : InternalApplet(NULL, name),
      DCOPObject("TaskbarApplet")
{
  KWM::keepOnTop(winId());

  KConfig *config = KGlobal::config();
  KConfigGroupSaver saver(config, "taskbar");

  setPosition((Position)config->readNumEntry("Position", (int)Bottom));

  show_all = config->readBoolEntry("ShowAllWindows", false);

  setFrameStyle(QFrame::StyledPanel | QFrame::Plain );

  connect( kwin_module, SIGNAL( windowAdd(WId) ), this, SLOT( windowAdd(WId) ) );
  connect( kwin_module, SIGNAL( windowRemove(WId) ), this, SLOT( windowRemove(WId) ) );
  connect( kwin_module, SIGNAL( windowChange(WId) ), this, SLOT( windowChange(WId) ) );
  connect( kwin_module, SIGNAL( windowActivate(WId) ), this, SLOT( windowActivate(WId) ) );
  connect( kwin_module, SIGNAL( desktopChange(int) ), this, SLOT( desktopChange(int) ) );

  setFlags(TopLevel);

  // register existing windows
  const QValueList<WId> windows = kwin_module->windows();
  QValueList<WId>::ConstIterator it;
  for ( it=windows.begin(); it!=windows.end(); ++it )
     windowAdd( *it );

  resetLayout();

  show();
}

TaskbarApplet::TaskbarApplet(QWidget* parent, const char* name )
    : InternalApplet(parent, name ), DCOPObject("TaskbarApplet")
{
  KConfig *config = KGlobal::config();
  KConfigGroupSaver saver(config, "taskbar");
  show_all = config->readBoolEntry("ShowAllWindows", false);

  setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);

  connect( kwin_module, SIGNAL( windowAdd(WId) ), this, SLOT( windowAdd(WId) ) );
  connect( kwin_module, SIGNAL( windowRemove(WId) ), this, SLOT( windowRemove(WId) ) );
  connect( kwin_module, SIGNAL( windowChange(WId) ), this, SLOT( windowChange(WId) ) );
  connect( kwin_module, SIGNAL( windowActivate(WId) ), this, SLOT( windowActivate(WId) ) );
  connect( kwin_module, SIGNAL( desktopChange(int) ), this, SLOT( desktopChange(int) ) );

  setFlags(Stretch);

  // register existing windows
  const QValueList<WId> windows = kwin_module->windows();
  QValueList<WId>::ConstIterator it;
  for ( it=windows.begin(); it!=windows.end(); ++it )
     windowAdd( *it );

  resetLayout();
}

TaskbarApplet::~TaskbarApplet() {}

void TaskbarApplet::killAppStartButton(pid_t pid)
{
  QMap<pid_t, AppStartButton *>::Iterator it = appsStarting.find(pid);

  if (it == appsStarting.end())
    return;

  AppStartButton * b = *it;

  if (0 != b) {
    kdDebug() << "Found a button to kill for pid " << long(pid) << "d" << endl;
    appsStarting.remove(pid);
    delete b;
    b = 0;
    updateLayout();
  }
}

void TaskbarApplet::resizeEvent(QResizeEvent*)
{
  resetLayout();
}

TaskButton* TaskbarApplet::findButton( WId w )
{
    for ( TaskButton* b = buttons.first(); b; b = buttons.next() ) {
	if ( b->window() == w )
	    return b;
    }
    return 0;
}

void TaskbarApplet::windowAdd(WId w )
{
    if ( w == topLevelWidget()->winId() )
        return;
    QString s = KWM::title(w);
    if(s.right(6) == "[menu]" || s.right(7) == "[tools]" ||
       s.right(6) == "Kicker" )
        return;

    pid_t pid = KWin::pid(w);

    if (-1 != pid)
      clientMapped(pid);

    TaskButton* b = new TaskButton(w, this);
    buttons.append( b );
    resetLayout();
}

void TaskbarApplet::windowRemove(WId w )
{
    if ( w == topLevelWidget()->winId() )
	return;
    TaskButton* b = findButton( w );
    if ( b ) {
	buttons.removeRef(b);
	delete b;
    resetLayout();
    }
}

void TaskbarApplet::windowChange(WId w)
{
    TaskButton* b = findButton( w );
    if ( !b )
	return;

    b->refresh();
    if ( b->onCurrentDesktop() != isVisible() ) {
	b->isVisible()?b->hide():b->show();
	resetLayout();
    }

}

void TaskbarApplet::windowActivate(WId w)
{
    if ( w == topLevelWidget()->winId() )
	return;
    TaskButton* b = findButton( w );
    if ( b )
	b->setActiveButton();
    else
	TaskButton::noActiveButton();
}

void TaskbarApplet::desktopChange(int )
{
    resetLayout();
}

int TaskbarApplet::widthForHeight(int h)
{
  lastH = h;
  return 200; // a default size (we are a stretch applet so this does not really matter)
}

int TaskbarApplet::heightForWidth(int)
{
  return 200; // a default size (we are a stretch applet so this does not really matter)
}

void TaskbarApplet::resetLayout()
{
    static QGridLayout *layout = NULL;
    if(layout)
        delete layout;
    bool horiz = orientation() == Horizontal;
    int items, i, col, row, mod = 0;

    if ( flags() == TopLevel && ( pos == Left || pos == Right ) )
      horiz = false;

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

    items += appsStarting.count();

    if(items){
      mod = (horiz && height() > 32) ? 2 : 1;
      layout = new QGridLayout(this,mod ,1 ,lineWidth());
    }
    else
      layout = NULL;

    for(i=0, col=0, row=0, b = buttons.first(); b; b = buttons.next()){
        if(b->onCurrentDesktop() || show_all){
            b->show();
            if(horiz){
                b->setMinimumSize(64, 8);
                b->setMaximumSize(200, 100);
                layout->addWidget(b, row, col);
                layout->setColStretch(col, 100);
            }
            else{
                b->setMinimumSize(8, 21);
                layout->addWidget(b, row, 0);
            }
            ++i;
            if ( horiz && ((i%mod) == 0) ) {
              row = 0;
              ++col;
            }
            else
              ++row;
        }
        else{
            b->move(0, 0);
            b->hide();
        }
    }

    QMap<pid_t, AppStartButton *>::Iterator it(appsStarting.begin());

    for (; it != appsStarting.end(); ++it) {
    
      AppStartButton * asb = *it;

      asb->show();

      if (horiz) {
        asb->setMinimumSize(64, 8);
        asb->setMaximumSize(200, 100);
        layout->addWidget(asb, row, col);
        layout->setColStretch(col, 100);
      } else {
        asb->setMinimumSize(8, 21);
        layout->addWidget(asb, row, 0);
      }

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

    if(layout){
        if(!horiz){ // hack since were using the taskbar as required stretch
            layout->setRowStretch(++row, 100);
        }
        else
            layout->setColStretch(++col, 1);
        layout->activate();
        updateGeometry();
    }
}

TaskButton* TaskButton::activeButton = 0;

void TaskButton::setActiveButton()
{
    setOn( TRUE );
    if ( activeButton && activeButton != this )
	activeButton->setOn( FALSE );
    setOn( TRUE );
    activeButton = this;
}

void TaskButton::noActiveButton()
{
    if ( activeButton )
	activeButton->setOn( FALSE );
    activeButton = 0;
}

TaskButton::TaskButton( WId w, QWidget * parent, const char *name )
    : QPushButton( parent, name )
{
    setToggleButton( TRUE );
    win = w;

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

    popup = 0;
    desk_popup = 0;
}

TaskButton::~TaskButton()
{
    if ( activeButton == this )
	activeButton = 0;
    delete popup; popup = 0;
}

bool TaskButton::onCurrentDesktop() const
{
    return sticky || kwin_module->currentDesktop() == desktop;
}

void TaskButton::refresh()
{
    pixmap = KWM::miniIcon( win, 16, 16 );
    if(pixmap.isNull())
        pixmap = SmallIcon("bx2");
    QString t = KWM::titleWithState( win );
    setText( t );
    QToolTip::add( this, t );
    desktop = KWM::desktop( win );
    sticky = KWM::isSticky( win );
}

void TaskButton::drawButton(QPainter *p)
{
    bool sunken = isOn() || isDown();
    if(kapp->kstyle()){
        kapp->kstyle()->drawKickerTaskButton(p, 0, 0, width(), height(),
                                             colorGroup(), text(), sunken,
                                             &pixmap, NULL);
        return;
    }
    kDrawNextButton(p, rect(), colorGroup(), sunken, sunken ?
                    &colorGroup().brush(QColorGroup::Mid) :
                    &colorGroup().brush(QColorGroup::Button));

    const int pxWidth = 20;
    QRect br( style().buttonRect( 0, 0, width(), height() ) );

    if ( !pixmap.isNull() ) {
        int dx = ( pxWidth - pixmap.width() ) / 2;
        int dy = ( height() - pixmap.height() ) / 2;
        if ( isDown() ) {
            dx++;
            dy++;
        }
        p->drawPixmap( br.x()+dx, dy, pixmap );
    }
    if(isOn() || isDown())
        p->setPen(colorGroup().light());
    else
        p->setPen(colorGroup().buttonText());

    if (!text().isNull()){
        QString s2 = text();
        if (fontMetrics().width(s2) > br.width()-pxWidth){
            while (s2.length()>0 &&
                   fontMetrics().width(s2) > br.width() - pxWidth
                   - fontMetrics().width("...")) {
                s2.truncate( s2.length() - 1 );
            }
            s2.append("...");
        }
        p->drawText(br.x()+ pxWidth, 0, width()-pxWidth, height(), AlignLeft|AlignVCenter, s2);
    }
}

void TaskButton::toggled()
{
    if ( isOn() ) {
	setActiveButton();
	KWin::setActiveWindow( win );
    }
    else
	KWM::setIconify( win, TRUE );
}

void TaskButton::clientPopupAboutToShow()
{
    if ( !popup )
        return;
    popup->setItemChecked( IconifyOp, KWM::isIconified( win ) );
    popup->setItemChecked( MaximizeOp, KWM::isMaximized( win ) );
}

void TaskButton::desktopPopupAboutToShow()
{
    if ( !desk_popup )
        return;
    desk_popup->clear();
    desk_popup->insertItem( i18n("&All desktops"), 0 );
    desk_popup->insertSeparator();
    if ( KWM::isSticky(win) )
        desk_popup->setItemChecked( 0, true );
    int id;
    for ( int i = 1; i <= KWin::numberOfDesktops(); i++ ) {
        id = desk_popup->insertItem( QString("&")+QString::number(i ), i );
        if ( !KWM::isSticky(win) && KWM::currentDesktop() == i )
            desk_popup->setItemChecked( id, TRUE );
    }
}

void TaskButton::clientPopupActivated( int id )
{
    switch ( id )
    {
    case MaximizeOp:
        KWM::setMaximize( win, !KWM::isMaximized( win ) );
        break;
    case IconifyOp:
        KWM::setIconify( win, !KWM::isIconified( win ) );
        break;
    case CloseOp:
        KWM::close( win );
        break;
    default:
        break;
    }
}

void TaskButton::sendToDesktop( int desk )
{
    if ( desk == 0 )
    {
        KWM::setSticky( win, !KWM::isSticky( win ) );
        return;
    }

    if ( KWM::isSticky( win ) )
        KWM::setSticky( win, false );

    if ( desk == KWin::currentDesktop() )
        return;

    KWM::moveToDesktop( win, 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 (!desk_popup) desk_popup = new QPopupMenu( popup );
            desk_popup->setCheckable( TRUE );
            connect( desk_popup, SIGNAL( aboutToShow() ),
                    this,       SLOT( desktopPopupAboutToShow() ) );
            connect( desk_popup, SIGNAL( activated(int) ),
                    this,       SLOT( sendToDesktop(int) ) );

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

            popup->insertSeparator();

            popup->insertItem(i18n("&To desktop"), desk_popup );

            popup->insertSeparator();

            popup->insertItem(i18n("&Close"), CloseOp);
        }
        popup->popup(e->globalPos());
        return;
    }
    QPushButton::mousePressEvent(e);
}

QSizePolicy TaskButton::sizePolicy() const
{
    return(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred));
}

QSize TaskButton::sizeHint() const
{
    return(minimumSize());
}

void TaskbarApplet::clientStarted(
  QString name,
  QString icon,
  pid_t pid
)
{
  kdDebug() << "clientStarted(" << name << ", " << icon << ", " << (long)pid << "d)" << endl;

  AppStartButton * b = new AppStartButton(name, icon, pid, this);
  appsStarting[pid] = b;

  connect(
    b,    SIGNAL(killMe(pid_t)),
    this, SLOT(killAppStartButton(pid_t)));

  resetLayout();
  b->show();
}

void TaskbarApplet::clientMapped(pid_t pid)
{
  kdDebug() << "clientMapped(" << (long)pid << "d)" << endl;
  killAppStartButton(pid);
}

void TaskbarApplet::clientDied(pid_t pid)
{
  kdDebug() << "clientDied(" << (long)pid << "d)" << endl;
  killAppStartButton(pid);
}

void TaskbarApplet::setPosition(Position p)
{
  // temporary stop avoiding while we are repositioning
  KWin::stopAvoiding(winId());

  pos = p;
  QRect r(KWin::edgeClientArea());
  switch (p)
  {
  case Top:
    resize(r.width(), 25);
    move(r.left(), r.top());
    KWin::avoid(winId(), KWin::Top);
    break;
  case Left:
    resize(150, 25);
    move(r.left(), r.top());
    KWin::avoid(winId(), KWin::Left);
    break;
  case Right:
    resize(150, 25);
    move(r.right() - width(), r.top());
    KWin::avoid(winId(), KWin::Right);
    break;
  case Bottom:
  default:
    resize(r.width(), 25);
    move(r.left(), r.bottom() - height());
//    KWin::avoid(winId(), KWin::Bottom);
    break;
  };

  KWin::updateClientArea();
}

///////////////////////////////////////////////////////////////
AppStartButton::AppStartButton(
  const QString & text,
  const QString & /*icon*/,
  pid_t pid,
  QWidget * parent
)
  : QPushButton(text, parent), frame(0), pid_(pid)
{

  QString appName(text);

  KService::Ptr service = KService::serviceByDesktopName(text);

  if (0 != service) {

    KDesktopFile desktopFile(service->desktopEntryPath(), true);
    appName = desktopFile.readName();
    setText(appName);
  }

  QToolTip::add(this, i18n("Starting %1").arg(appName));

  anim.setAutoDelete(true);

  for (int i = 1; i < 9; i++)
    anim.append(new QPixmap(UserIcon(QString("disk") + QString::number(i))));

  connect(&animTimer, SIGNAL(timeout()), this, SLOT(animTimerFired()));

  animTimer.start(100);

  // Go away after 20s if we weren't removed before.
  startTimer(20000);
}

void AppStartButton::timerEvent(QTimerEvent *)
{
  killTimers();
  emit(killMe(pid_));
}

void AppStartButton::animTimerFired()
{
  QPainter p(this);

  p.drawPixmap(4, 4, *anim.at(frame));

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

void AppStartButton::drawButton(QPainter *p)
{
    if(kapp->kstyle()){
        kapp->kstyle()->drawKickerTaskButton(p, 0, 0, width(), height(),
                                             colorGroup(), text(),
                                             isOn() || isDown(),
                                             NULL, NULL);
        return;
    }

    kDrawNextButton(p, rect(), colorGroup(), false,
                    &colorGroup().brush(QColorGroup::Button));

  const int pxWidth = 20;

  QRect br(style().buttonRect(0, 0, width(), height()));

  if (isOn() || isDown())
      p->setPen(colorGroup().light());
  else
      p->setPen(colorGroup().buttonText());

  int ellipsisWidth = fontMetrics().width("...");

  if (!text().isNull()) {

    QString s(text());

    if (fontMetrics().width(s) > br.width() - pxWidth) {

      while (
        !s.isEmpty() &&
        fontMetrics().width(s) > br.width() - pxWidth - ellipsisWidth
      )
      {
        s.truncate( s.length() - 1 );
      }
      s.append("...");
    }

    p->drawText(
      br.x() + pxWidth, 0,
      width() - pxWidth, height(),
      AlignLeft | AlignVCenter,
      s
    );

  }

  if (!text().isNull()) {

    QString s(text());

    if (fontMetrics().width(s) > br.width()) {

        while(
          !s.isEmpty() && fontMetrics().width(s) > br.width() - ellipsisWidth
        )
        {
            s.truncate( s.length() - 1 );
        }

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

    p->drawText(
      br.x() + pxWidth, 0,
      width() - pxWidth, height(),
      AlignLeft | AlignVCenter,
      s
    );
  }
}

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

QSizePolicy AppStartButton::sizePolicy() const
{
  return(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred));
}

QSize AppStartButton::sizeHint() const
{
  return(minimumSize());
}

