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

Copyright (c) 2001 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 <stdlib.h>
#include <math.h>

#include <qlayout.h>
#include <qtooltip.h>
#include <qtimer.h>
#include <qcursor.h>
#include <qmovie.h>

#include <kwin.h>
#include <kconfig.h>
#include <klocale.h>
#include <kglobalsettings.h>
#include <kwin.h>
#include <kwinmodule.h>
#include <kapplication.h>
#include <kdebug.h>
#include <karrowbutton.h>
#include <netwm.h>
#include <fixx11h.h>

#include "kicker.h"
#include "panelop_mnu.h"
#include "panelbuttonbase.h"
#include "userrectsel.h"
#include "container_base.h"
#include "panelmanager.h"

#include "container_panel.h"
#include "container_panel.moc"

const int XineramaAllScreens = -2;

KWinModule* PanelContainer::kWinModule = 0;

PanelSettings::PanelSettings()
{
    // defaults
    _position          = ::Bottom;
    _alignment         = QApplication::reverseLayout() ? ::RightBottom : ::LeftTop;
    _HBwidth           = 14;
    
//    _showLeftHB        = QApplication::reverseLayout();
//    _showRightHB       = !QApplication::reverseLayout();

     _showLeftHB        = false;
     _showRightHB       = true;
    
    _hideMode          = Manual;
    _autoHideSwitch    = false;
    _autoHideDelay     = 3;
    _hideAnim          = true;
    _hideAnimSpeed     = 40;
    _unhideLocation    = UnhideTrigger::BottomLeft;
    _sizePercentage    = 100;
    _expandSize        = true;
    _resizeablehandle   = false;
    _size              = Normal;
    _customSize        = 58;
    _showToolTips      = true;

    // Basic Xinerama support - position itself on the U-L head, along the bottom
    _xineramaScreen = QApplication::desktop()->primaryScreen();
}

void PanelSettings::readConfig( KConfig *c )
{
    _position          = static_cast<Position>(
                         c->readNumEntry(  "Position",               _position));
    _alignment         = static_cast<Alignment>(
                         c->readNumEntry(  "Alignment",              _alignment));
    _xineramaScreen    = c->readNumEntry(  "XineramaScreen",         _xineramaScreen);
    _HBwidth           = c->readNumEntry(  "HideButtonSize",         _HBwidth);
    _showLeftHB        = c->readBoolEntry( "ShowLeftHideButton",     _showLeftHB);
    _showRightHB       = c->readBoolEntry( "ShowRightHideButton",    _showRightHB);
    // For backwards compatibility, we don't just store the enum.
    if( c->readBoolEntry( "AutoHidePanel", _hideMode == Automatic ) ) {
       _hideMode = Automatic;
    } else if( c->readBoolEntry( "BackgroundHide", _hideMode == Background )) {
       _hideMode = Background;
    } else {
       _hideMode = Manual;
    }
    _autoHideSwitch    = c->readBoolEntry( "AutoHideSwitch",         _autoHideSwitch);
    _autoHideDelay     = c->readNumEntry(  "AutoHideDelay",          _autoHideDelay);
    _hideAnim          = c->readBoolEntry( "HideAnimation",          _hideAnim);
    _hideAnimSpeed     = c->readNumEntry(  "HideAnimationSpeed",     _hideAnimSpeed);
    _unhideLocation    = static_cast<UnhideTrigger::Trigger>(
                         c->readNumEntry(  "UnhideLocation",         _unhideLocation));
    _sizePercentage    = c->readNumEntry(  "SizePercentage",         _sizePercentage );
    _expandSize        = c->readBoolEntry( "ExpandSize",             _expandSize );
    _resizeablehandle   = c->readBoolEntry( "ResizeableHandle", false );
    int sizeSetting    = c->readNumEntry(  "Size",                   static_cast<int>(_size));
    _customSize        = c->readNumEntry(  "CustomSize",             _customSize);

    // sanitize
    if (sizeSetting < Tiny || sizeSetting > Custom) _size = Normal;
    else _size = static_cast<Size>(sizeSetting);

    if (_customSize < 1) _customSize = Kicker::MIN_DIMENSION;

    if (_HBwidth < 3) _HBwidth = 3;
    else if (_HBwidth > 24) _HBwidth = 24;

    if ( _sizePercentage < 1 ) _sizePercentage = 1;
    else if ( _sizePercentage > 100 ) _sizePercentage = 100;
}

void PanelSettings::writeConfig( KConfig *c ) {
    c->writeEntry( "Position",               static_cast<int>(_position));
    c->writeEntry( "Alignment",              static_cast<int>(_alignment));
    c->writeEntry( "XineramaScreen",         _xineramaScreen);
    c->writeEntry( "HideButtonSize",         _HBwidth);
    c->writeEntry( "ShowLeftHideButton",     _showLeftHB);
    c->writeEntry( "ShowRightHideButton",    _showRightHB);
    c->writeEntry( "AutoHidePanel",          _hideMode == Automatic );
    c->writeEntry( "BackgroundHide",         _hideMode == Background );
    c->writeEntry( "AutoHideSwitch",         _autoHideSwitch);
    c->writeEntry( "AutoHideDelay",          _autoHideDelay);
    c->writeEntry( "HideAnimation",          _hideAnim);
    c->writeEntry( "HideAnimationSpeed",     _hideAnimSpeed);
    c->writeEntry( "UnhideLocation",         static_cast<int>(_unhideLocation));
    c->writeEntry( "SizePercentage",         _sizePercentage );
    c->writeEntry( "ExpandSize",             _expandSize );
    c->writeEntry( "ResizeableHandle",         _resizeablehandle );
    c->writeEntry( "Size",                   static_cast<int>(_size));
    c->writeEntry( "CustomSize",             _customSize);
}

PanelContainer::PanelContainer(QWidget *parent, const char *name)
   : QFrame(parent, name, WStyle_Customize | WStyle_NoBorder)
  , _autoHidden(false)
  , _userHidden(Unhidden)
  , _block_user_input(false)
  , _is_lmb_down(false)
  , _in_autohide(false)
{
    if( kWinModule == 0 ) {
         kWinModule = new KWinModule();
    }

    // panels live in the dock
    KWin::setType( winId(), NET::Dock );
    KWin::setState( winId(), NET::Sticky );
    KWin::setOnAllDesktops( winId(), TRUE );

    connect( kWinModule, SIGNAL( strutChanged() ), this, SLOT( strutChanged() ) );
    connect( kWinModule, SIGNAL( currentDesktopChanged(int) ),
	     this, SLOT( currentDesktopChanged(int) ) );

    setFrameStyle( NoFrame );
    setLineWidth( 0 );
    setMargin(0);

    connect( UnhideTrigger::the(), SIGNAL(triggerUnhide(UnhideTrigger::Trigger,int)),
             SLOT(unhideTriggered(UnhideTrigger::Trigger,int)) );

    _popupWidgetFilter = new PopupWidgetFilter( this );
    connect( _popupWidgetFilter, SIGNAL(popupWidgetHiding()), SLOT(maybeStartAutoHideTimer()) );

    // layout
   
    /* This is actually a work arround some miss implementations in QT
        in the constructor, we passed the direction, however QT is "smart"
	enough to reverse it in RTL desktops. We need this widget to be "LTR"
	always. (remeber that we are in reverseLayout).
	
	QT4 will have a special API to tell a layout "you shall be always LTR". Now,
	we hack it.
    */    
    QBoxLayout::Direction dir;
    
    if (QApplication::reverseLayout())
	dir = orientation() == Horizontal ? QBoxLayout::RightToLeft  : QBoxLayout::TopToBottom;
    else
	dir = orientation() == Horizontal ? QBoxLayout::LeftToRight  : QBoxLayout::TopToBottom;
    
    _layout = new QBoxLayout( this, dir, 0, 0);
    _layout->setResizeMode( QLayout::FreeResize );
    
    // left/top hide button
    _ltHB = new KArrowButton(this);
    _ltHB->installEventFilter( this );
    connect( _ltHB, SIGNAL( clicked() ), this, SLOT( hideLeft() ) );
    
    _layout->addWidget( _ltHB );

    // right/bottom hide button
    _rbHB = new KArrowButton(this);
    _rbHB->installEventFilter( this );
    connect( _rbHB, SIGNAL( clicked() ), this, SLOT( hideRight() ) );
    
    _layout->addWidget( _rbHB );

    // instantiate the autohide timer
    _autohideTimer = new QTimer(this);
    connect(_autohideTimer, SIGNAL(timeout()), SLOT(autoHideTimeout()));

    installEventFilter( this ); // for mouse event handling

    PanelManager::the()->add( this );
}

PanelContainer::~PanelContainer()
{
    PanelManager::the()->remove( this );
}

void PanelContainer::closeEvent( QCloseEvent* e )
{
    // Prevent being closed via Alt-F4
    e->ignore();
}

int PanelContainer::xineramaScreen() const
{
    // sanitize at runtime only, since many Xinerama users
    // turn it on and off and don't want kicker to lose their configs

    /* -2 means all screens, -1 primary screens, the rest are valid screen numbers */
    if (XineramaAllScreens <= _settings._xineramaScreen && _settings._xineramaScreen < QApplication::desktop()->numScreens())
        return _settings._xineramaScreen;
    else
      /* force invalid screen locations onto the primary screen */
      return QApplication::desktop()->primaryScreen();
}

void PanelContainer::setMainWidget( QWidget* main )
{
   main->reparent( this, QPoint( 0,0 ) );
   _layout->insertWidget( 1, main, 1 );
}

PanelSettings PanelContainer::defaultSettings() const
{
    return PanelSettings();
}

static bool isnetwm12_below()
{
    NETRootInfo info( qt_xdisplay(), NET::Supported );
    return info.supportedProperties()[ NETRootInfo::STATES ] & NET::KeepBelow;
}

void PanelContainer::readConfig( KConfig* config )
{
//    kdDebug(1210) << "PanelContainer::readConfig()" << endl;

    _settings = defaultSettings();
    _settings.readConfig( config );

    emit positionChange( position() );
    emit alignmentChange( alignment() );
    emit sizeChange( _settings._size, _settings._customSize );

    updateLayout();

    if( _settings._hideMode != PanelSettings::Automatic )
	autoHide(false);

    static bool netwm12 = isnetwm12_below();
    if( netwm12 ) // new netwm1.2 compliant way
    {
    if( _settings._hideMode == PanelSettings::Background ) {
	KWin::setState( winId(), NET::KeepBelow );
	UnhideTrigger::the()->setEnabled( true );
    } else {
	KWin::clearState( winId(), NET::KeepBelow );
    }
    }
    else // old way
    {
    if( _settings._hideMode == PanelSettings::Background ) {
	KWin::clearState( winId(), NET::StaysOnTop );
	UnhideTrigger::the()->setEnabled( true );
    } else {
	KWin::setState( winId(), NET::StaysOnTop );
    }
    }
    maybeStartAutoHideTimer();
}

void PanelContainer::writeConfig( KConfig* config )
{
//    kdDebug(1210) << "PanelContainer::writeConfig()" << endl;

    _settings.writeConfig( config );
}

void PanelContainer::arrange( Position p, Alignment a, int XineramaScreen )
{
    if ( p == _settings._position && a == _settings._alignment && XineramaScreen == xineramaScreen())
	return;

    if ( p != _settings._position ) {
	_settings._position = p;
	emit positionChange( p );
    }

    if ( a != _settings._alignment ) {
	_settings._alignment = a;
	emit alignmentChange( a );
    }

   if( XineramaScreen != xineramaScreen() ) {
	_settings._xineramaScreen = XineramaScreen;
	emit xineramaScreenChange( XineramaScreen );
    }

    updateLayout();
    writeConfig();
}


void PanelContainer::setResizeableHandle( bool resizeablehandle )
{
    _settings._resizeablehandle = resizeablehandle;
    emit resizeablehandleChange( resizeablehandle );

    updateLayout();
    writeConfig();
}

void PanelContainer::setSize( Size size, int customSize )
{
    if ( size == _settings._size &&
         customSize == _settings._customSize)
        return;

    _settings._size = size;
    _settings._customSize = customSize;

    emit sizeChange( _settings._size, _settings._customSize );

    updateLayout();
    writeConfig();
}

Size PanelContainer::size() const
{
    return _settings._size;
}

int PanelContainer::customSize() const
{
    return _settings._customSize;
}

Qt::Orientation PanelContainer::orientation() const
{
    if( position() == ::Top || position() == ::Bottom ) {
	return Horizontal;
    } else {
	return Vertical;
    }
}

void PanelContainer::currentDesktopChanged(int)
{
//    kdDebug(1210) << "PanelContainer::currentDesktopChanged" << endl;
    if (_settings._autoHideSwitch&&_settings._hideMode == PanelSettings::Automatic) {
	autoHide(false);
    }

    // For some reason we don't always get leave events when the user
    // changes desktops and moves the cursor out of the panel at the
    // same time. Maybe always calling this will help.
    maybeStartAutoHideTimer();
}

void PanelContainer::hideLeft()
{
    animatedHide(true);
}

void PanelContainer::hideRight()
{
    animatedHide(false);
}

void PanelContainer::resetLayout()
{
    QRect g = initialGeometry( position(), alignment(), xineramaScreen(), autoHidden(), userHidden() );

    setGeometry( g );
    
    // layout
    if( orientation() == Horizontal )
	_layout->setDirection( QApplication::reverseLayout()?QBoxLayout::RightToLeft:QBoxLayout::LeftToRight );
    else
	_layout->setDirection( QBoxLayout::TopToBottom );

   // left/top hide button
    if ( orientation() == Horizontal ) {
	_ltHB->setArrowType(Qt::LeftArrow);
	_ltHB->setFixedSize(_settings._HBwidth, height());
    } else {
	_ltHB->setArrowType(Qt::UpArrow);
	_ltHB->setFixedSize(width(), _settings._HBwidth);
    }

    if ( _settings._showLeftHB || userHidden() == RightBottom ) {
	_ltHB->show();
    } else {
	_ltHB->hide();
    }

    // right/bottom hide button
    if ( orientation() == Horizontal ) {
	_rbHB->setArrowType(Qt::RightArrow);
	_rbHB->setFixedSize(_settings._HBwidth, height());
    } else {
	_rbHB->setArrowType(Qt::DownArrow);
	_rbHB->setFixedSize(width(), _settings._HBwidth);
    }

    if ( _settings._showRightHB || userHidden() == LeftTop ) {
	_rbHB->show();
    } else {
	_rbHB->hide();
    }
    QToolTip::remove( _ltHB );
    QToolTip::remove( _rbHB );
    if( userHidden() ) {
	QToolTip::add(_ltHB, i18n("Show panel"));
	QToolTip::add(_rbHB, i18n("Show panel"));
    } else {
	QToolTip::add(_ltHB, i18n("Hide panel"));
	QToolTip::add(_rbHB, i18n("Hide panel"));
    }

    _layout->activate();
    updateGeometry();

}

void PanelContainer::blockUserInput( bool block )
{
    if ( block == _block_user_input )
	return;

    // If we don't want any user input to be possible we should catch mouse
    // events and such. Therefore we install an eventfilter and let the
    // eventfilter discard those events.
    if ( block )
	qApp->installEventFilter( this );
    else
	qApp->removeEventFilter( this );

    _block_user_input = block;
}

// The autohide logic is pretty hacky.
//
// How it should work:
//
// The autohide timer is a single shot. It is started whenever the mouse
// leaves the panel and autohide is on. When the timeout occurs, which
// could be as soon as the event queue is clear if the timeout is zero,
// the panel is autohidden and the timer is stopped. When the mouse reenters
// the panel, it is un-autohidden.
//
// Why this won't work:
//
// 1) We don't want to hide the panel if there is a popup menu showing,
//    even if the mouse is outside the panel.
// 3) We don't get reliable enter/leave events during a virtual desktop
//    change.
// 2) The container area (and some applets) grab the mouse at various
//    times, notably during a button press or moving an applet. This
//    causes us not to receive enter and leave events.
//
// What we do instead:
//
// the autohide timer is continouously running whenever autohide is enabled
// and the panel is not hidden. When it times out, if the cursor is not inside
// the panel or there are no popup menus open, the panel is autohidden. If a
// popup is open, we stop the timer, and install an event filter on the
// popup what will restart the timer when it closes. If the cursor is inside the
// panel, the timer keeps running. If the user specified a zero timeout, run it at
// 10 ms instead to avoid taking 100% cpu. If we do get an enter or leave event,
// reset the timer so that the desired timout will be followed.
//
// Known problems with this approach:
//
// We take more cpu then we should. Still a few cases where panel
// should be autohid, but isn't.
//
// Very bizzare and unfathomable problem: Look below, in the
// autoHideTimeout method. You will see a call to autoHide(true)
// sandwiched between two debug statements. If you look at
// autoHide(), you will see that it prints lots of debug info.
// Here is the strange part: sometimes, autoHide(true) _DOES NOT_
// get called, even when the debug strings surrounding the call
// are printed! WTF?! This happens if you drag an applet handle outside
// the panel and wait for the timeout. I am completely mystified
// by this.
void PanelContainer::unhideTriggered( UnhideTrigger::Trigger tr, int XineramaScreen )
{
    if( _settings._hideMode == PanelSettings::Manual )
        return;

    if( _settings._hideMode == PanelSettings::Background ) {
        if( _settings._unhideLocation == tr )
        {
            UnhideTrigger::the()->triggerAccepted( tr, XineramaScreen );
            KWin::forceActiveWindow( winId());
        }
        return;
    }

    // Otherwise hide mode is automatic. The code below is slightly
    // complex so as to keep the same behavior as it has always had:
    // only unhide when the cursor position is within the widget geometry.
    // We can't just do geometry().contains(QCursor::pos()) because
    // now we hide the panel completely off screen.
    if (xineramaScreen() != XineramaAllScreens && XineramaScreen != xineramaScreen())
        return;

    int x = QCursor::pos().x();
    int y = QCursor::pos().y();
    int t = geometry().top();
    int b = geometry().bottom();
    int r = geometry().right();
    int l = geometry().left();
    if( ((tr == UnhideTrigger::Top || tr == UnhideTrigger::TopLeft || tr == UnhideTrigger::TopRight)
          && position() == ::Top && x >= l && x <= r )
     || ((tr == UnhideTrigger::Left || tr == UnhideTrigger::TopLeft || tr == UnhideTrigger::BottomLeft)
          && position() == ::Left && y >= t && y <= b )
     || ((tr == UnhideTrigger::Bottom || tr == UnhideTrigger::BottomLeft || tr == UnhideTrigger::BottomRight)
          && position() == ::Bottom && x >= l && x <= r )
     || ((tr == UnhideTrigger::Right || tr == UnhideTrigger::TopRight || tr == UnhideTrigger::BottomRight)
          && position() == ::Right && y >= t && y <= b ) )
    {
        UnhideTrigger::the()->triggerAccepted( tr, XineramaScreen );

        if ( _autoHidden )
        {
            autoHide(false);
            maybeStartAutoHideTimer();
        }
    }
}

void PanelContainer::maybeStartAutoHideTimer()
{
    if ( _settings._hideMode == PanelSettings::Automatic && !_autoHidden && !_userHidden ) {


//	kdDebug(1210) << "starting auto hide timer for " << name() << endl;

	if( _settings._autoHideDelay == 0 )
	    _autohideTimer->start( 10 );
	else
	    _autohideTimer->start( _settings._autoHideDelay * 1000 );
    }
}

void PanelContainer::stopAutoHideTimer()
{
   if ( _autohideTimer->isActive() ) {
//	kdDebug(1210) << "stopping auto hide timer for " << name() << endl;
	_autohideTimer->stop();
   }
}

void PanelContainer::autoHideTimeout()
{
//    kdDebug(1210) << "PanelContainer::autoHideTimeout() " << name() << endl;

    // Hack: If there is a popup open, don't autohide until it closes.
    QWidget* popup = QApplication::activePopupWidget();
    if ( popup ) {

//	kdDebug(1210) << "popup detected" << endl;

	// Remove it first in case it was already installed.
	// Does nothing if it wasn't installed.
	popup->removeEventFilter( _popupWidgetFilter );

	// We will get a signal from the filter after the
	// popup is hidden. At that point, maybeStartAutoHideTimer()
	// will get called again.
	popup->installEventFilter( _popupWidgetFilter );

	// Stop the timer.
	stopAutoHideTimer();
	return;
    }

    if( _settings._hideMode != PanelSettings::Automatic || _autoHidden || _userHidden )
	return;

    if( vetoAutoHide() )
	return;

    QRect r = geometry();
    QPoint p = QCursor::pos();
    if( !r.contains(p) ) {

	stopAutoHideTimer();

//	kdDebug(1210) << "cursor is outside panel, hiding" << endl;
	// Sometimes, this call isn't made, even though the debug messages
	// on either side are printed!!!!!
	autoHide(true);
        UnhideTrigger::the()->resetTriggerThrottle();
//	kdDebug(1210) << "autoHide(true) was just called" << endl;
    }
}

bool PanelContainer::eventFilter( QObject*, QEvent * e)
{

    if (autoHidden()) {
	switch ( e->type() ) {
	case QEvent::MouseButtonPress:
	case QEvent::MouseButtonRelease:
	case QEvent::MouseButtonDblClick:
	case QEvent::MouseMove:
	case QEvent::KeyPress:
	case QEvent::KeyRelease:
	    return true; // ignore;
	default:
	    break;
	}
    }

    if ( _block_user_input ) {
	switch ( e->type() ) {
	case QEvent::MouseButtonPress:
	case QEvent::MouseButtonRelease:
	case QEvent::MouseButtonDblClick:
	case QEvent::MouseMove:
	case QEvent::KeyPress:
	case QEvent::KeyRelease:
	case QEvent::Enter:
	case QEvent::Leave:
	    return true; // ignore;
	default:
	    break;
	}
	return false;
    }

    switch ( e->type() ) {
    case QEvent::MouseButtonPress:
	{
	    QMouseEvent* me = (QMouseEvent*) e;
	    if ( me->button() == LeftButton )
		{
		    _last_lmb_press = me->globalPos();
		    _is_lmb_down = true;
		}
	    else if ( me->button() == RightButton )
		{
		    showPanelMenu( me->globalPos() );
		    return TRUE; // don't crash!
		}
	}
	break;
    case QEvent::MouseButtonRelease:
	{
	    QMouseEvent* me = (QMouseEvent*) e;
	    if ( me->button() == LeftButton )
		{
		    _is_lmb_down = false;
		}
	}
	break;
    case QEvent::MouseMove:
	{
	    QMouseEvent* me = (QMouseEvent*) e;
	    if ( _is_lmb_down && ((me->state() & LeftButton) == LeftButton) &&
	         !Kicker::kicker()->isImmutable() ) {
		QPoint p( me->globalPos() - _last_lmb_press );
		int x_treshold = width();
		int y_treshold = height();
		if (x_treshold > y_treshold)
		   x_treshold = x_treshold / 3;
		else
		   y_treshold = y_treshold / 3;
		if ( (abs(p.x()) > x_treshold) || (abs(p.y()) > y_treshold) ) {
		    moveMe();
		    return true;
		}
	    }
	}
	break;
    default:
	break;
    }
    return false;
}

QSize PanelContainer::initialSize( Position p, int XineramaScreen )
{
    QRect a = workArea(XineramaScreen);

//    kdDebug(1210) << "     Work Area: (" << a.topLeft().x() << ", " << a.topLeft().y() << ") to ("
//                                 << a.bottomRight().x() << ", " << a.bottomRight().y() << ")" << endl;

    QSize hint = sizeHint( p, a.size() ).boundedTo( a.size() );
    int width = 0;
    int height = 0;

    if( p == ::Left || p == ::Right ) {
	width = hint.width();
	height = ( a.height() * _settings._sizePercentage ) / 100;

	if ( _settings._expandSize ) {
	    height = QMAX( height, hint.height() );
	}
    } else {
	width = ( a.width() * _settings._sizePercentage ) / 100;
	height = hint.height();

	if ( _settings._expandSize ) {
	    width = QMAX( width, hint.width() );
	}
    }

    return QSize( width, height );
}

QPoint PanelContainer::initialLocation( Position p, Alignment a, int XineramaScreen, const QSize &s, bool autohidden, UserHidden userHidden )
{
    QRect area = workArea(XineramaScreen);

    int left;
    int top;

    // If the panel is horizontal
    if ( p == ::Top || p == ::Bottom) {
	// Get the X coordinate
	switch ( a ) {
	    case ::LeftTop:
		left = area.left();
		break;

	    case ::Center:
		left = area.left() + ( area.width() - s.width() ) / 2;
		break;

	    case ::RightBottom:
		left = area.right() - s.width() + 1;
		break;

	    default:
		left = area.left();
	}

	// Get the Y coordinate
	if ( p == ::Top )
	    top = area.top();
	else
	    top = area.bottom() - s.height() + 1;
    }

    // If the panel is vertical
    else
    {
	// Get the Y coordinate
	switch ( a ) {
	    case ::LeftTop:
		top = area.top();
		break;

	    case ::Center:
		top = area.top() + ( area.height() - s.height() ) / 2;
		break;

	    case ::RightBottom:
		top = area.bottom() - s.height() + 1;
		break;

	    default:
		top = area.top();
	}

	// Get the X coordinate
	if ( p == ::Left )
	    left = area.left();
	else
	    left = area.right() - s.width() + 1;
    }

    // Correct for auto hide
    if( autohidden ) {
	switch( position() ) {
	case ::Left:
	    left -= s.width();
	    break;
	case ::Right:
	    left += s.width();
	    break;
	case ::Top:
	    top -= s.height();
	    break;
	case ::Bottom:
	default:
	    top += s.height();
	    break;
	}

    // Correct for user hide
    } else if ( userHidden == LeftTop ) {
	switch( position() ) {
	case ::Left:
	case ::Right:
	    top = area.top() - s.height() + _settings._HBwidth;
	    break;
	case ::Top:
	case ::Bottom:
	default:
	    left = area.left() - s.width() + _settings._HBwidth;
	    break;
	}
    } else if ( userHidden == RightBottom ) {
	switch( position() ) {
	case ::Left:
	case ::Right:
	    top = area.bottom() - _settings._HBwidth + 1;
	    break;
	case ::Top:
	case ::Bottom:
	default:
	    left = area.right() - _settings._HBwidth + 1;
	    break;
	}
    }
    return QPoint( left, top );
}

QRect PanelContainer::initialGeometry( Position p, Alignment a, int XineramaScreen, bool autoHidden, UserHidden userHidden )
{
//    kdDebug(1210) << "   Computing geometry for " << name() << endl;

    QSize size = initialSize( p, XineramaScreen );
    QPoint point = initialLocation( p, a, XineramaScreen, size, autoHidden, userHidden );

//    kdDebug(1210) << "     Size: " << size.width() << " x " << size.height() << endl;
//    kdDebug(1210) << "     Pos: (" << point.x() << ", " << point.y() << ")" << endl;

    return QRect( point, size );
}

void PanelContainer::updateLayout()
{
//    kdDebug(1210) << "PanelContainer::updateLayout()" << endl;
    resetLayout();
    updateWindowManager();
}

void PanelContainer::strutChanged()
{
//    kdDebug(1210) << "PanelContainer::strutChanged()" << endl;
    if ( initialGeometry( position(), alignment(), xineramaScreen(), autoHidden(), userHidden() ) != geometry() ) {
	updateLayout();
    }
}

void PanelContainer::updateWindowManager()
{
//    kdDebug(1210) << "PanelContainer::updateWindowManager()" << endl;
    // Set the relevant properties on the window.
    int w = width();
    int h = height();

		QRect geom = initialGeometry( position(), alignment(), xineramaScreen() );
    QRect virtRect(QApplication::desktop()->geometry());
    QRect screenRect(QApplication::desktop()->screenGeometry(xineramaScreen()));

    if( userHidden() || _settings._hideMode != PanelSettings::Manual )
	w = h = 0;

    NETStrut strut;
    //FIXME do not set the strut if the edge we are placing ourselves along is not also an edge
    //of the virual screen, since NETWM has no way to represent the resulting non-rectangular
    //client areas that can result in an Xinerama environment.
    //Remove these checks if NetWM ever gets smarter about this (either struts that don't take
    //up an entire edge or per-screen struts
    switch (position()) {
    case ::Top:     if(screenRect.top() == virtRect.top())
			strut.top = geom.y() + h;
		    break;
    case ::Bottom:  if(screenRect.bottom() == virtRect.bottom())
			strut.bottom = (virtRect.bottom() - geom.bottom()) + h;
		    break;
    case ::Right:   if(screenRect.right() == virtRect.right())
			strut.right = (virtRect.right() - geom.right()) + w;
		    break;
    case ::Left:    if(screenRect.left() == virtRect.left())
			strut.left = geom.x() + w;
		    break;
    }

    // only call KWin::setStrut when the strut is really changed
    if ( strut.left   != _strut.left  ||
         strut.right  != _strut.right ||
         strut.top    != _strut.top   ||
         strut.bottom != _strut.bottom ) {

	_strut = strut;
//	kdDebug(1210) << "Panel sets new strut: " << position() << endl;
//	kdDebug(1210) << strut.top << " " << strut.bottom << " " << strut.right << " " << strut.left << endl;
	KWin::setStrut( winId(), strut.left, strut.right,  strut.top, strut.bottom );
    }
}

QSize PanelContainer::sizeHint( Position p, const QSize &maxSize )
{
    int width = 0;
    int height = 0;
    if( p == ::Top || p == ::Bottom ) {
	if ( _settings._showLeftHB )
	    width += _settings._HBwidth;
	if ( _settings._showRightHB )
	    width += _settings._HBwidth;
    } else {
	if ( _settings._showLeftHB )
	    height += _settings._HBwidth;
	if ( _settings._showRightHB )
	    height += _settings._HBwidth;
    }

    QSize size = QSize( width, height ).boundedTo( maxSize );

    //kdDebug(1210) << "     PanelContainer requests " << size.width() << " x " << size.height() << endl;

    return size;
}

/* 1 is the initial speed, hide_show_animation is the top speed. */
#define PANEL_SPEED(x, c) (int)((1.0-2.0*fabs((x)-(c)/2.0)/c)*_settings._hideAnimSpeed+1.0)

void PanelContainer::animatedHide(bool left)
{
//    kdDebug(1210) << "PanelContainer::animatedHide()" << endl;
    PanelButtonBase::setZoomEnabled(false);

    blockUserInput(true);

    UserHidden newState;
    if( _userHidden != Unhidden )
        newState = Unhidden;
    else if( left )
	newState = LeftTop;
    else
        newState = RightBottom;

    QPoint oldpos = pos();
    QRect newextent = initialGeometry( position(), alignment(), xineramaScreen(), false, newState );
    QPoint newpos(newextent.topLeft());

    if( newState != Unhidden ) {
	/* bail out if we are unable to hide */
	for(int s=0; s <  QApplication::desktop()->numScreens(); s++) {
	    /* don't let it intersect with any screen in the hidden position
	     * that it doesn't intesect in the shown position. Should prevent
	     * panels from hiding by sliding onto other screens, while still
	     * letting them show reveal buttons onscreen */
	    if(QApplication::desktop()->screenGeometry(s).intersects(newextent)
	    && !QApplication::desktop()->screenGeometry(s).intersects(geometry())) {
		blockUserInput( false );
		return;
	    }
	}

	_userHidden = newState;

	// So we don't cover the mac-style menubar
	lower();
    }

    if( _settings._hideAnim ) {
	switch( position() ) {
	case Left:
	case Right:
	    for (int i = 0; i < abs(newpos.y() - oldpos.y());
		 i += PANEL_SPEED(i,abs(newpos.y() - oldpos.y())))
		{
		    if (newpos.y() > oldpos.y())
			move(newpos.x(), oldpos.y() + i);
		    else
			move(newpos.x(), oldpos.y() - i);
		    qApp->syncX();
		    qApp->processEvents();
		}
	    break;
	case Top:
	case Bottom:
	default:
	    for (int i = 0; i < abs(newpos.x() - oldpos.x());
		 i += PANEL_SPEED(i,abs(newpos.x() - oldpos.x())))
		{
		    if (newpos.x() > oldpos.x())
			move(oldpos.x() + i, newpos.y());
		    else
			move(oldpos.x() - i, newpos.y());
		    qApp->syncX();
		    qApp->processEvents();
		}
	    break;
	}
    }

    blockUserInput( false );

    _userHidden = newState;

    updateLayout();
    qApp->syncX();
    qApp->processEvents();
    QTimer::singleShot(1000, this, SLOT(enableZoomedIcons()));
}

void PanelContainer::enableZoomedIcons()
{
    PanelButtonBase::setZoomEnabled(true);
}

void PanelContainer::autoHide(bool hide)
{
//   kdDebug(1210) << "PanelContainer::autoHide( " << hide << " )" << endl;

   if ( _in_autohide ) {
//	kdDebug(1210) << "in autohide" << endl;
	return;
   }

    if ( hide == _autoHidden ) {
//	kdDebug(1210) << "already in that state" << endl;
	return; //nothing to do
    }

//    kdDebug(1210) << "entering autohide for real" << endl;

    blockUserInput(true);

    QPoint oldpos = pos();
    QRect newextent = initialGeometry( position(), alignment(), xineramaScreen(), hide, Unhidden );
    QPoint newpos = newextent.topLeft();

    if ( hide ) {
	/* bail out if we are unable to hide */

	for(int s=0; s <  QApplication::desktop()->numScreens(); s++) {
 	    /* don't let it intersect with any screen in the hidden position
	     * that it doesn't intesect in the shown position. Should prevent
	     * panels from hiding by sliding onto other screens, while still
	     * letting them show reveal buttons onscreen */
	    if(QApplication::desktop()->screenGeometry(s).intersects(newextent)
	    && !QApplication::desktop()->screenGeometry(s).intersects(geometry())) {
		blockUserInput( false );
		return;
	    }
	}
    }
    _in_autohide = true;

    PanelButtonBase::setZoomEnabled(false);

    _autoHidden = hide;
    UnhideTrigger::the()->setEnabled( _autoHidden );

    if( hide ) {
	// So we don't cover other panels
	lower();
    } else {
	// So we aren't covered by other panels
	raise();
    }

    if(_settings._hideAnim) {
	switch( position() ) {
	case Left:
	case Right:
	    for (int i = 0; i < abs(newpos.x() - oldpos.x());
		 i += PANEL_SPEED(i,abs(newpos.x() - oldpos.x())))
		{
		    if (newpos.x() > oldpos.x())
			move(oldpos.x() + i, newpos.y());
		    else
			move(oldpos.x() - i, newpos.y());
		    qApp->syncX();
		    qApp->processEvents();
		}
	    break;
	case Top:
	case Bottom:
	default:
	    for (int i = 0; i < abs(newpos.y() - oldpos.y());
		 i += PANEL_SPEED(i,abs(newpos.y() - oldpos.y())))
		{
		    if (newpos.y() > oldpos.y())
			move(newpos.x(), oldpos.y() + i);
		    else
			move(newpos.x(), oldpos.y() - i);
		    qApp->syncX();
		    qApp->processEvents();
		}
	    break;
	}
    }

    blockUserInput(false);

    updateLayout();

    // Sometimes tooltips don't get hidden
    QToolTip::hide();

    _in_autohide = false;

    qApp->syncX();
    qApp->processEvents();
    QTimer::singleShot(1000, this, SLOT(enableZoomedIcons()));
}

void PanelContainer::moveMe()
{
    stopAutoHideTimer();

    QApplication::syncX();
    QValueList<QRect> rects;

    // Move this panel to the top of the stacking list
    PanelManager::the()->raise( this );

    Position  positions[]  = { ::Left, ::Right, ::Top, ::Bottom };
    Alignment alignments[] = { ::LeftTop, ::Center, ::RightBottom };
    for(int s = 0; s < QApplication::desktop()->numScreens(); s++) {
        for( int i = 0; i < 4; i++ ) {
            for( int j = 0; j < 3; j++ ) {
                rects.append( initialGeometry( positions[i], alignments[j], s ) );
            }
        }
    }

    int index = xineramaScreen() * 12
              + position() * 3
              + alignment();
    QPoint offset = 2*(rects[index].center() - _last_lmb_press)/3;
    int rect = UserRectSel::select( rects, index, offset );
    int XineramaScreen = rect / 12;
    Position p = (Position)((rect / 3) % 4);
    Alignment a = (Alignment)(rect % 3);
    arrange(p, a, XineramaScreen);

    _is_lmb_down = false;

    // sometimes the HB's are not reset correctly
    _ltHB->setDown(false);
    _rbHB->setDown(false);

    maybeStartAutoHideTimer();
}

QRect PanelContainer::workArea(int XineramaScreen)
{
    QValueList<WId> list;

    PanelListIterator it( PanelManager::the()->panels() );

    // If the hide mode is Manual, exclude the struts of
    // panels below this one in the list. Else exclude the
    // struts of all panels.
    if( _settings._hideMode == PanelSettings::Manual ) {
        if (XineramaScreen != XineramaAllScreens) {
            for( int i = 0 ; i < PanelManager::the()->stackingPosition( this ); ++i, ++it ) {
                KWin::WindowInfo wi = KWin::windowInfo( ( *it )->winId() );
                if ( !wi.geometry().intersects( QApplication::desktop()->screenGeometry(XineramaScreen) ) )
                    list.append( (*it)->winId());
            }
        } else {
            it += PanelManager::the()->stackingPosition( this );
        }
    }

    for( ; it.current(); ++it ) {
	list.append((*it)->winId());
    }

    if(XineramaScreen == XineramaAllScreens) /* special value for all screens */
	return kWinModule->workArea(list);
    else
	return kWinModule->workArea(list, XineramaScreen).intersect(QApplication::desktop()->screenGeometry(XineramaScreen));
}

PopupWidgetFilter::PopupWidgetFilter( QObject *parent )
  : QObject( parent, "PopupWidgetFilter" )
{
}

bool PopupWidgetFilter::eventFilter( QObject*, QEvent* e )
{
    if( e->type() == QEvent::Hide ) {
	emit popupWidgetHiding();
    }
    return false;
}
