/*
 * advwidget.cpp - AdvancedWidget template class
 * Copyright (C) 2005  Michail Pishchagin
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "advwidget.h"

#include <qapplication.h>
#include <qwidgetlist.h>
#include <qtimer.h>

#ifdef Q_OS_WIN
#include <windows.h>
#include <winuser.h>
#endif

// TODO: Make use of KDE taskbar flashing support
// TODO: Use FlashWindowEx instead of FlashWindow!

//----------------------------------------------------------------------------
// AdvancedWidgetShared
//----------------------------------------------------------------------------

class AdvancedWidgetShared : public QObject
{
	Q_OBJECT
public:
	AdvancedWidgetShared();
	~AdvancedWidgetShared();
};

AdvancedWidgetShared::AdvancedWidgetShared()
	: QObject(qApp)
{
}

AdvancedWidgetShared::~AdvancedWidgetShared()
{
}

static AdvancedWidgetShared *advancedWidgetShared = 0;

//----------------------------------------------------------------------------
// GAdvancedWidget::Private
//----------------------------------------------------------------------------

class GAdvancedWidget::Private : public QObject
{
	Q_OBJECT
public:
	Private(QWidget *parent);

	static int  stickAt;
	static bool stickToWindows;
	static bool stickEnabled;

	QWidget *parentWidget;

	bool flashing();
	void doFlash(bool on);
	void posChanging(int *x, int *y, int *width, int *height);

private:
	QTimer *flashTimer;
	int flashCount;

private slots:
	void flashAnimate();
};

int  GAdvancedWidget::Private::stickAt        = 5;
bool GAdvancedWidget::Private::stickToWindows = true;
bool GAdvancedWidget::Private::stickEnabled   = true;

GAdvancedWidget::Private::Private(QWidget *parent)
	: QObject(parent)
{
	if ( !advancedWidgetShared )
		advancedWidgetShared = new AdvancedWidgetShared();

	parentWidget = parent;
	flashTimer = 0;
	flashCount = 0;
}

void GAdvancedWidget::Private::posChanging(int *x, int *y, int *width, int *height)
{
	if ( stickAt <= 0                    ||
	    !stickEnabled                    ||
	    !parentWidget->isTopLevel()      ||
	     parentWidget->isMaximized()     ||
	    !parentWidget->isUpdatesEnabled() )
	{
		return;
	}

	QWidget *p = parentWidget;
	if ( p->pos() == QPoint(*x, *y) &&
	     p->frameSize() == QSize(*width, *height) )
		return;

	bool resizing = p->frameSize() != QSize(*width, *height);

	QWidget *w;
	QDesktopWidget *desktop = qApp->desktop();
	QWidgetList *list;

	if ( stickToWindows )
		list = QApplication::topLevelWidgets();
	else
		list = new QWidgetList();
	list->append( desktop );

	QWidgetListIt it( *list );
	for ( ; (w = it.current()); ++it ) {
		QRect rect;
		bool dockWidget = false;

		if ( w->isDesktop() )
			rect = ((QDesktopWidget *)w)->availableGeometry((QWidget *)parent());
		else {
			if ( w == p ||
			     desktop->screenNumber(p) != desktop->screenNumber(w) )
				continue;

			if ( !w->isVisible() )
				continue;

			// we want for widget to stick to outer edges of another widget, so
			// we'll change the rect to what it'll snap
			rect = QRect(w->frameGeometry().bottomRight(), w->frameGeometry().topLeft());
			dockWidget = true;
		}

		if ( *x != p->x() )
		if ( *x <= rect.left() + stickAt &&
		     *x >  rect.left() - stickAt ) {
			if ( !dockWidget ||
			     (p->frameGeometry().bottom() >= rect.bottom() &&
			      p->frameGeometry().top() <= rect.top()) ) {
				*x = rect.left();
				if ( resizing )
					*width = p->frameSize().width() + p->x() - *x;
			}
		}

		if ( *x + *width >= rect.right() - stickAt &&
		     *x + *width <= rect.right() + stickAt ) {
			if ( !dockWidget ||
			     (p->frameGeometry().bottom() >= rect.bottom() &&
			      p->frameGeometry().top() <= rect.top()) ) {
				if ( resizing )
					*width = rect.right() - *x + 1;
				else
					*x = rect.right() - *width + 1;
			}
		}

		if ( *y != p->y() )
		if ( *y <= rect.top() + stickAt &&
		     *y >  rect.top() - stickAt ) {
			if ( !dockWidget ||
			     (p->frameGeometry().right() >= rect.right() &&
			      p->frameGeometry().left() <= rect.left()) ) {
				*y = rect.top();
				if ( resizing )
					*height = p->frameSize().height() + p->y() - *y;
			}
		}

		if ( *y + *height >= rect.bottom() - stickAt &&
		     *y + *height <= rect.bottom() + stickAt ) {
			if ( !dockWidget ||
			     (p->frameGeometry().right() >= rect.right() &&
			      p->frameGeometry().left() <= rect.left()) ) {
				if ( resizing )
					*height = rect.bottom() - *y + 1;
				else
					*y = rect.bottom() - *height + 1;
			}
		}
	}

	delete list;
}

void GAdvancedWidget::Private::doFlash(bool yes)
{
#ifdef Q_WS_WIN
	if ( yes ) {
		if ( flashTimer )
			return;
		flashTimer = new QTimer(this);
		connect(flashTimer, SIGNAL(timeout()), SLOT(flashAnimate()));
		flashCount = 0;
		flashAnimate(); // kick the first one immediately
		flashTimer->start(500);
	}
	else {
		if ( flashTimer ) {
			delete flashTimer;
			flashTimer = 0;
			// comment this out to fix titlebar repaint on Windows??
			//FlashWindow(winId(), false);
		}
	}
#else
	Q_UNUSED(yes)
#endif
}

void GAdvancedWidget::Private::flashAnimate()
{
#ifdef Q_WS_WIN
	FlashWindow( parentWidget->winId(), true );
	if ( ++flashCount == 5 )
		flashTimer->stop();
#endif
}

bool GAdvancedWidget::Private::flashing()
{
	bool on = false;
	if ( flashTimer )
		on = flashCount & 1;

	return on;
}

//----------------------------------------------------------------------------
// GAdvancedWidget
//----------------------------------------------------------------------------

GAdvancedWidget::GAdvancedWidget(QWidget *parent, const char *name)
	: QObject(parent, name)
{
	d = new Private(parent);
}

GAdvancedWidget::~GAdvancedWidget()
{
	delete d;
}

#ifdef Q_OS_WIN
bool GAdvancedWidget::winEvent(MSG *msg)
{
	if ( msg->message == WM_WINDOWPOSCHANGING ) {
		WINDOWPOS *wpos = (WINDOWPOS *)msg->lParam;

		d->posChanging(&wpos->x, &wpos->y, &wpos->cx, &wpos->cy);

		return true;
	}

	return false;
}
#endif

void GAdvancedWidget::preSetCaption()
{
#ifdef Q_WS_WIN
	if ( d->flashing() )
		FlashWindow( d->parentWidget->winId(), true );
#endif
}

void GAdvancedWidget::postSetCaption()
{
#ifdef Q_WS_WIN
	if ( d->flashing() )
		FlashWindow( d->parentWidget->winId(), true );
#endif
}

void GAdvancedWidget::doFlash(bool on)
{
	d->doFlash( on );
}

void GAdvancedWidget::windowActivationChange(bool oldstate)
{
	if ( d->parentWidget->isActiveWindow() ) {
		d->doFlash(false);
	}
}

int GAdvancedWidget::stickAt()
{
	return Private::stickAt;
}

void GAdvancedWidget::setStickAt(int val)
{
	Private::stickAt = val;
}

bool GAdvancedWidget::stickToWindows()
{
	return Private::stickToWindows;
}

void GAdvancedWidget::setStickToWindows(bool val)
{
	Private::stickToWindows = val;
}

bool GAdvancedWidget::stickEnabled()
{
	return Private::stickEnabled;
}

void GAdvancedWidget::setStickEnabled(bool val)
{
	Private::stickEnabled = val;
}

#include "advwidget.moc"
