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

    kpager.cpp  - KPager's main window
    Copyright (C) 2000  Antonio Larrosa Jimenez <larrosa@kde.org>
			Matthias Ettrich
			Matthias Elter
    Copyright (C) 2010-2011  Pino Toscano <pino@kde.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

    Send comments and bug fixes to larrosa@kde.org

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

/*
 * There is a features that is only configurable by manually editing the
 * config file due to the translation freeze . The key is
 * windowTransparentMode and the values are :
 *    0 = Never
 *    1 = Only maximized windows are painted transparent
 *    2 = Every window is painted transparent (default)
 *
 */

#include "kpager.h"
#include "config.h"

#include <QTimer>
#include <QShowEvent>
#include <QGridLayout>
#include <QMenu>
#include <QX11Info>
#include <QDesktopWidget>
#include <QApplication>

#include <kaction.h>
#include <kactioncollection.h>
#include <kstandardaction.h>
#include <kconfig.h>
#include <kdebug.h>
#include <klocale.h>
#include <kwindowsystem.h>
#include <netwm.h>
#include "desktop.h"
#include <kmenu.h>
#include <kicon.h>

#include "kpageradaptor.h"
#include <settings.h>
#include <QtDBus/QtDBus>

static int intFromItem(KConfigSkeletonItem *item)
{
    bool ok = false;
    const int x = item->property().toInt(&ok);
    return ok ? x : -1;
}

KPagerMainWindow::KPagerMainWindow(QWidget *parent)
	: KXmlGuiWindow(parent)
{
    m_reallyClose=false;

    m_pPager = new KPager(this);
    setCentralWidget(m_pPager);

    // Update the last used geometry
    int w = intFromItem(Settings::self()->findItem(m_pPager->lWidth()));
    int h = intFromItem(Settings::self()->findItem(m_pPager->lHeight()));
    if (w > 0 && h > 0)
        resize(w,h);
    else
        resize(m_pPager->sizeHint());
    //  resize(cfg->readEntry(lWidth(),200),cfg->readEntry(lHeight(),90));

    int xpos = Settings::xPos();
    int ypos = Settings::yPos();
    if (xpos > 0 && ypos > 0)
      move(xpos,ypos);
    else
    {
//      NETRootInfo ri( QX11Info::display(), NET::WorkArea );
//      NETRect rect=ri.workArea(1);
//      move(rect.pos.x+rect.size.width-m_pPager->width(),
//	  rect.pos.y+rect.size.height-m_pPager->height());
// antonio:The above lines don't work. I should look at them when I have
// more time
        move(qApp->desktop()->width()-m_pPager->sizeHint().width()-5,qApp->desktop()->height()-m_pPager->sizeHint().height()-25);
    }

    // Set the wm flags to this window
    KWindowSystem::setState( winId(), NET::StaysOnTop | NET::SkipTaskbar | NET::Sticky | NET::SkipPager );
    KWindowSystem::setOnAllDesktops( winId(), true);
    KWindowSystem::setType( winId(), NET::Utility );

    XWMHints *hints = XGetWMHints(QX11Info::display(), winId());
    if( hints == NULL )
        hints = XAllocWMHints();
    hints->input = false;
    hints->flags |= InputHint;
    XSetWMHints(QX11Info::display(), winId(), hints);
    XFree(reinterpret_cast<char *>(hints));

    timeout=new QTimer(this);
    timeout->setObjectName("timeoutToQuit");
    timeout->setSingleShot(true);
    connect(timeout,SIGNAL(timeout()),this, SLOT(reallyClose()));

    new KPagerAdaptor(this);
    QDBusConnection::sessionBus().registerObject("/Pager", this);
}

KPagerMainWindow::~KPagerMainWindow()
{
}

extern bool closed_by_sm;

bool KPagerMainWindow::queryClose()
{
    Settings::setXPos(x());
    Settings::setYPos(y());
    Settings::self()->findItem(m_pPager->lWidth())->setProperty(width());
    Settings::self()->findItem(m_pPager->lHeight())->setProperty(height());
    Settings::self()->writeConfig();

    kDebug() << "queryCLose " << m_reallyClose << " " << closed_by_sm;
    if (m_reallyClose || closed_by_sm) return true;

    hide();
    timeout->start(10 /*minutes*/ *60000);

    return false;
}

bool KPagerMainWindow::queryExit()
{
    Settings::setXPos(x());
    Settings::setYPos(y());
    Settings::self()->findItem(m_pPager->lWidth())->setProperty(width());
    Settings::self()->findItem(m_pPager->lHeight())->setProperty(height());
    Settings::self()->writeConfig();

    return true;
}

void KPagerMainWindow::showEvent( QShowEvent *ev )
{
  timeout->stop();
  KXmlGuiWindow::showEvent(ev);
}

void KPagerMainWindow::reallyClose()
{
  m_reallyClose=true;
  close();
}

void KPagerMainWindow::showAt(int x, int y)
{
  // Just in case we lost the sticky bit... (as when a window is hidden)
  KWindowSystem::setOnAllDesktops( winId(), true);

  if (x>qApp->desktop()->width()/2) // Right
    x-=m_pPager->width()+5;
  if (y>qApp->desktop()->height()/2) // Bottom
    y-=m_pPager->height()+25;
  move(x,y);
  show();
}

void KPagerMainWindow::toggleShow(int x, int y)
{
  if (isVisible())
  {
    hide();
    timeout->start(10 /*minutes*/ *60000);
  }
  else
    showAt(x,y);
}

KPager::KPager(KPagerMainWindow *parent)
  : QWidget(parent)
    , m_activeWin(0)
    , m_mnu(0)
    , m_smnu(0)
    , m_dmnu(0)
{
    QPalette pal = palette();
    pal.setColor( backgroundRole(), Qt::black );
    setPalette( pal );
    m_currentDesktop=KWindowSystem::currentDesktop();

    m_grabWinTimer=new QTimer( this );
    m_grabWinTimer->setObjectName( "grabWinTimer" );
    m_grabWinTimer->setSingleShot(true);
    connect(m_grabWinTimer, SIGNAL(timeout()), this, SLOT(slotGrabWindows()));

    int numberOfDesktops=KWindowSystem::numberOfDesktops();
    for (int i=0;i<numberOfDesktops;i++)
    {
        Desktop *dsk=new Desktop(i+1,this);
        m_desktops.append(dsk);
    }

    m_layoutType = static_cast<enum KPager::LayoutTypes>(Settings::layoutType());

    connect( KWindowSystem::self(), SIGNAL( activeWindowChanged(WId)),
             SLOT(slotActiveWindowChanged(WId)));
    connect( KWindowSystem::self(), SIGNAL( windowAdded(WId) ),
             SLOT( slotWindowAdded(WId) ) );
    connect( KWindowSystem::self(), SIGNAL( windowRemoved(WId) ),
             SLOT( slotWindowRemoved(WId) ) );
    connect( KWindowSystem::self(), SIGNAL( windowChanged(WId,unsigned int) ),
             SLOT( slotWindowChanged(WId,unsigned int) ) );
    connect( KWindowSystem::self(), SIGNAL( stackingOrderChanged() ),
             SLOT( slotStackingOrderChanged() ) );
    connect( KWindowSystem::self(), SIGNAL( desktopNamesChanged() ),
             SLOT( slotDesktopNamesChanged() ) );
    connect( KWindowSystem::self(), SIGNAL( numberOfDesktopsChanged(int) ),
             SLOT( slotNumberOfDesktopsChanged(int) ) );
    connect( KWindowSystem::self(), SIGNAL( currentDesktopChanged(int)),
             SLOT( slotCurrentDesktopChanged(int) ) );

    setFont(Settings::font());

    m_prefs_action = parent->actionCollection()->addAction( KStandardAction::Preferences, this, SLOT(configureDialog()) );
    m_quit_action = parent->actionCollection()->addAction( KStandardAction::Quit, qApp, SLOT(quit()) );

    updateLayout();
}

KPager::~KPager()
{
    qDeleteAll(m_windows);

}

const QString KPager::lWidth() const
{
    switch (m_layoutType) {
	case (Classical) :  return "layoutClassicalWidth";break;
	case (Horizontal) : return "layoutHorizontalWidth";break;
	case (Vertical) :   return "layoutVerticalWidth";break;
    };
    return "Width";
}

const QString KPager::lHeight() const
{
    switch (m_layoutType) {
	case (Classical) :  return "layoutClassicalHeight";break;
	case (Horizontal) : return "layoutHorizontalHeight";break;
	case (Vertical) :   return "layoutVerticalHeight";break;
    };
    return "Height";
}

void KPager::updateLayout()
{
    int w=m_desktops[0]->width();
    int h=m_desktops[0]->height();

    delete layout();
    QGridLayout *layout = new QGridLayout(this);
    layout->setMargin(0);
    layout->setSpacing(0);

    QList <Desktop *>::Iterator it;
    int i,j;
    i=j=0;
    int ndesks=0;
    int halfdesks = (m_desktops.count() + 1) / 2;
    for( it = m_desktops.begin(); it != m_desktops.end(); ++it )
    {
        layout->addWidget(*it,i,j);
        ndesks++;
        switch (m_layoutType)
        {
            case (Classical) :  i= ndesks / halfdesks; j = ndesks % halfdesks; break;
            case (Horizontal) : j++; break;
            case (Vertical) :   i++; break;
        };
    }

    layout->activate();
    updateGeometry();

    switch (m_layoutType)
    {
        case (Classical) :  resize(w*(ndesks/2+(ndesks%2)),h*2);break;
        case (Horizontal) : resize(w*ndesks,h);break;
        case (Vertical) :   resize(w, h*ndesks);break;
    };

}

void KPager::showPopupMenu(WId wid, const QPoint &pos)
{
    if (wid <= 0) {
	if(!m_smnu) {
	    m_smnu = new QMenu(this);
	    m_smnu->addAction( m_prefs_action );
	    m_smnu->addAction( m_quit_action );
	}
	m_smnu->popup(pos);
    }
    else {
        m_winfo = KWindowSystem::windowInfo(wid, NET::WMName | NET::WMState | NET::XAWMState | NET::WMDesktop);

        if (!m_mnu) {
            m_mnu = new KMenu(this);

#ifdef HAVE_FIXED_KMENU_TITLE
            m_mnu_title = m_mnu->addTitle( QString::fromUtf8("KPager"));
#else
            m_mnu->addTitle( QString::fromUtf8("KPager"));
#endif
            connect(m_mnu, SIGNAL(aboutToShow()), SLOT(clientPopupAboutToShow()));
            connect(m_mnu, SIGNAL(triggered(QAction*)), SLOT(clientPopupActivated(QAction*)));

            m_dmnu = new QMenu(i18n("&To Desktop"), m_mnu);
            connect(m_dmnu, SIGNAL(aboutToShow()), SLOT(desktopPopupAboutToShow()));
            connect(m_dmnu, SIGNAL(triggered(QAction*)), SLOT(sendToDesktop(QAction*)));


            m_iconify_action = m_mnu->addAction( i18n("Mi&nimize") );
            m_iconify_action->setData( IconifyOp );
            m_iconify_action->setCheckable( true );

            m_maximize_action = m_mnu->addAction( i18n("Ma&ximize") );
            m_maximize_action->setData( MaximizeOp );
            m_maximize_action->setCheckable( true );

            m_sticky_action = m_mnu->addAction( i18n("&Sticky") );
            m_sticky_action->setData( StickyOp );
            m_sticky_action->setCheckable( true );

            m_mnu->addSeparator();

            m_mnu->addMenu( m_dmnu );
            m_mnu->addSeparator();

            QAction *action = m_mnu->addAction( i18n("&Close") );
            action->setIcon( KIcon("window-close") );
            action->setData( CloseOp );

            m_mnu->addSeparator();

            m_mnu->addAction( m_prefs_action );
            m_mnu->addAction( m_quit_action );
        }
        m_mnu->popup(pos);
    }
}

void KPager::configureDialog()
{
    if (KConfigDialog::showDialog("configdialog"))
        return;

    KPagerConfigDialog *dialog = new KPagerConfigDialog(this);
    connect(dialog, SIGNAL(settingsChanged(QString)), this, SLOT(slotSettingsChanged()));

    dialog->show();
}

void KPager::slotSettingsChanged()
{
    m_layoutType = static_cast<enum KPager::LayoutTypes>(Settings::layoutType());
    int nWd = parentWidget() ? parentWidget()->width() : width();
    int nHg = parentWidget() ? parentWidget()->width() : width();

    Settings::self()->findItem(lWidth())->setProperty(nWd);
    Settings::self()->findItem(lHeight())->setProperty(nHg);

    updateLayout();
    foreach (Desktop *desk, m_desktops)
        desk->update();
}

KWindowInfo* KPager::info( WId win )
{
    KWindowInfo* info = m_windows.value(win);
    if (!info )
    {
        info = new KWindowInfo( KWindowSystem::windowInfo( win,
            NET::WMName | NET::WMState | NET::XAWMState | NET::WMDesktop
            | NET::WMWindowType | NET::WMFrameExtents | NET::WMGeometry ));
        if( !info->valid() || info->desktop() == 0 )
        {
            delete info;
            return NULL; // window no longer valid
        }
        m_windows.insert( win, info );
    }
    return info;
}

void KPager::slotActiveWindowChanged( WId win )
{
    KWindowInfo* inf1 = info( m_activeWin );
    KWindowInfo* inf2 = info( win );
    m_activeWin = win;

    // update window pixmap
    // in case of active desktop change it will be updated anyway by timer
//    if (!m_grabWinTimer->isActive())
//        Desktop::removeCachedPixmap(win);

    const int desk1 = inf1 ? inf1->desktop() : 0;
    const int desk2 = inf2 ? inf2->desktop() : 0;
    // if either the previous or the current active window was on all desktops,
    // update all of them
    if ((desk1 == NET::OnAllDesktops) || (desk2 == NET::OnAllDesktops)) {
        foreach (Desktop *d, m_desktops) {
            d->update();
        }
    } else {
        if (desk1 > 0) {
            m_desktops.at(desk1 - 1)->update();
        }
        if ((desk2 > 0) && (desk2 != desk1)) {
            m_desktops.at(desk2 - 1)->update();
        }
    }
}

void KPager::slotWindowAdded( WId win)
{
    KWindowInfo* inf = info( win );
    if (!inf)
        return; // never should be here

    updateDesktop(inf->desktop());
}

void KPager::slotWindowRemoved( WId win )
{
    KWindowInfo* inf = m_windows.value(win);
    if (inf)
    {
        m_windows.remove( win );
        Desktop::removeCachedPixmap(win);
        updateDesktop(inf->desktop());
        delete inf;
    }
}

void KPager::slotWindowChanged( WId win , unsigned int prop)
{
    bool repaint=false;

    KWindowInfo* inf = m_windows.value(win);
    if (!inf)
    {
      inf=info(win);
      prop=0; // info already calls KWindowSystem::info, so there's no need
      // to update anything else.
      repaint=true;
    };

    bool onAllDesktops = inf ? inf->onAllDesktops() : false;
    int desktop = inf ? inf->desktop() : 0;

    if (prop)
    {
      delete m_windows[win];
      m_windows.remove( win );
      inf = info( win );
    }

    if((prop & ~( NET::WMName | NET::WMVisibleName )) != 0 )
	repaint = true;

    if (repaint)
    for ( int i=1; i <= m_desktops.count(); ++i)
    {
      if ((inf && (inf->isOnDesktop(i)))
	  || onAllDesktops || desktop == i )
        {
            m_desktops[i-1]->update();
        }
    }
//	redrawDesktops();
}

void KPager::slotStackingOrderChanged()
{
    m_desktops[m_currentDesktop-1]->m_grabWindows=true;
    for ( int i=1; i <= m_desktops.count(); ++i)
    {
        m_desktops[i-1]->update();
    }
//    update(true);
}

void KPager::slotDesktopNamesChanged()
{
    for ( int i=1; i <= m_desktops.count(); ++i)
    {
        m_desktops[i-1]->updateDesktopName();
    }
}

void KPager::slotNumberOfDesktopsChanged(int nDesktops)
{
    if (nDesktops<m_desktops.count())
    {
        QList <Desktop *>::Iterator it;
        for ( int i=m_desktops.count()-nDesktops; i > 0; i--)
        {
            delete m_desktops.at( m_desktops.size() - 1 );
            m_desktops.removeAt( m_desktops.size() - 1 );
        }

        emit updateLayout();
    }
    else if (nDesktops>m_desktops.count())
    {
        int i,j;
        i=j=m_desktops.count();
        switch (m_layoutType)
        {
            case (Classical) :  i%=2;j/=2; break;
            case (Horizontal) : i=0; break;
            case (Vertical) :   j=0; break;
        }

        for (int d=m_desktops.count()+1;d<=nDesktops; d++)
        {
            Desktop *dsk=new Desktop(d,this);
            m_desktops.append(dsk);
            dsk->show();
        }

        emit updateLayout();
    }
}


void KPager::slotCurrentDesktopChanged(int desk)
{
    if (m_currentDesktop==desk) return;
//     m_desktops[m_currentDesktop-1]->paintFrame( false );
    m_desktops[m_currentDesktop-1]->update();
//     m_desktops[desk-1]->paintFrame( true );
    m_desktops[desk-1]->update();
//    m_desktops[m_currentDesktop-1]->update();
//    m_desktops[desk-1]->update();

    m_currentDesktop=desk;

    if (m_grabWinTimer->isActive()) m_grabWinTimer->stop();

    if ( static_cast<Desktop::WindowDrawMode>(Settings::windowDrawMode()) == Desktop::Pixmap )
        m_grabWinTimer->start(1000);
}

void KPager::slotBackgroundChanged(int desk)
{
    m_desktops[desk-1]->loadBgPixmap();
}

void KPager::sendToDesktop(QAction *action)
{
    int desk = action->data().toInt();

    if (desk == 0)
        KWindowSystem::setOnAllDesktops(m_winfo.win(), true);
    else {
        KWindowSystem::setOnDesktop(m_winfo.win(), desk);
    }
}

void KPager::clientPopupAboutToShow()
{
    if (!m_mnu) return;

#ifdef HAVE_FIXED_KMENU_TITLE
    m_mnu_title->setText(m_winfo.name() );
    m_mnu_title->setIcon( KWindowSystem::icon(m_winfo.win(),16,16,true) );
#else
    QAction *act = m_mnu->actions().first();
    m_mnu->removeAction(act);
    delete act;
    m_mnu->addTitle(KWindowSystem::icon(m_winfo.win(), 16, 16, true),
                    m_winfo.name(), m_mnu->actions().first());
#endif
    m_iconify_action->setChecked( m_winfo.isMinimized() );
    m_maximize_action->setChecked( m_winfo.state() & NET::Max );
    m_sticky_action->setText( m_winfo.onAllDesktops() ? i18n("Un&sticky") : i18n("&Sticky") );
}

void KPager::desktopPopupAboutToShow()
{
    if (!m_dmnu) return;

    m_dmnu->clear();
    QAction *action = m_dmnu->addAction( i18n("&All Desktops") );
    action->setData( 0 );
    action->setCheckable( true );
    m_dmnu->addSeparator();

    if (m_winfo.onAllDesktops())
        action->setChecked( true );

    for (int i = 1; i <= m_desktops.size(); ++i) {
        action = m_dmnu->addAction( QString("&")+QString::number(i )+QString(" ")
                                 + m_desktops.at(i - 1)->deskName().replace('&', "&&") );
        action->setData( i );
        action->setCheckable( true );
        if ( m_winfo.desktop() == i )
            action->setChecked( true );
    }
}

void KPager::clientPopupActivated( QAction *action )
{
    int id = action->data().toInt();

    switch ( id ) {
	case MaximizeOp:
	    if ( (m_winfo.state() & NET::Max)  == 0 ) {
		NETWinInfo ni( QX11Info::display(), m_winfo.win(), QX11Info::appRootWindow(), 0);
		ni.setState( NET::Max, NET::Max );
	    } else {
		NETWinInfo ni( QX11Info::display(), m_winfo.win(), QX11Info::appRootWindow(), 0);
		ni.setState( 0, NET::Max );
	    }
	    break;
	case IconifyOp:
	    if ( !m_winfo.isMinimized() ) {
		KWindowSystem::minimizeWindow( m_winfo.win());
	    } else {
		KWindowSystem::forceActiveWindow( m_winfo.win() );
	    }
	    break;
	case StickyOp:
	    if ( m_winfo.onAllDesktops() ) {
		KWindowSystem::setOnAllDesktops(m_winfo.win(), false);
	    } else {
		KWindowSystem::setOnAllDesktops(m_winfo.win(), true);
	    }
	    break;
	case CloseOp: {
	    NETRootInfo ri( QX11Info::display(), 0 );
	    ri.closeWindowRequest( m_winfo.win() );
	} break;
	default:
	    break;
    }
}

void KPager::redrawDesktops()
{
    QList <Desktop *>::Iterator it;
    for( it = m_desktops.begin(); it != m_desktops.end(); ++it )
        (*it)->update();
}

void KPager::slotGrabWindows()
{
    m_desktops[m_currentDesktop-1]->m_grabWindows=true;
    m_desktops[m_currentDesktop-1]->update();
}

QSize KPager::sizeHint() const
{
    int n=m_desktops.count();
    int w=-1,h=-1;

    QSize size=m_desktops[0]->sizeHint();
    int wDsk=size.width();
    int hDsk=size.height();
    switch (m_layoutType)
    {
        case (Classical) :  w=wDsk*(n/2+(n%2)); h=hDsk*2;break;
        case (Horizontal) : w=wDsk*n; h=hDsk;break;
        case (Vertical) :   w=wDsk; h=hDsk*n;break;
    };
    return QSize(w,h);
}

void KPager::updateDesktop(int desk)
{
    if (desk == NET::OnAllDesktops) {
        foreach (Desktop *d, m_desktops) {
            d->update();
        }
    } else if (desk > 0) {
        m_desktops.at(desk - 1)->update();
    }
}

#include "kpager.moc"
