#include "group.h"

/**
 *
 * Beryl group plugin
 *
 * tab.c
 *
 * Copyright : (C) 2006 by Patrick Niklaus, Roi Cohen, Danny Baumann
 * Authors: Patrick Niklaus <patrick.niklaus@googlemail.com>
 *          Roi Cohen       <roico@beryl-project.org>
 *          Danny Baumann   <maniac@beryl-project.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.
 *
 **/

/*
 * groupGetCurrentMousePosition
 *
 */
Bool groupGetCurrentMousePosition(CompScreen *s, int *x, int *y)
{
	unsigned int rmask;
	int mouseX, mouseY, winX, winY;
	Window root;
	Window child;
	Bool result;
	
	result = XQueryPointer (s->display->display, s->root, &root, 
			&child, &mouseX, &mouseY, &winX, &winY, &rmask);
	
	if (result) {
		(*x) = mouseX;
		(*y) = mouseY;
	}

	return result;
}

/*
 * groupGetWindowTitle - mostely copied from state.c
 *
 */
static char *groupGetWindowTitle(CompWindow *w)
{
	Atom type;
	int format;
	unsigned long nitems;
	unsigned long bytes_after;
	unsigned long *val;
	Display *d = w->screen->display->display;
	char *retval;
	int result;
	Atom utf8_string;

	utf8_string = XInternAtom(d, "UTF8_STRING", 0);

	type = None;
	val = NULL;
	result = XGetWindowProperty(d, w->id,
		XInternAtom(d, "_NET_WM_NAME", 0), 0, LONG_MAX,	False, 
		utf8_string, &type, &format, &nitems, &bytes_after, 
		(unsigned char **)&val);

	if (result != Success)
		return NULL;

	if (type != utf8_string || format != 8 || nitems == 0)
	{
		if (val)
			XFree(val);
		return NULL;
	}


	retval = (char *)malloc(sizeof(char) * (nitems + 1));
	strncpy(retval, (char *)val, nitems);
	retval[nitems] = '\0';

	XFree(val);

	return retval;
}

/*
 * groupGetClippingRegion
 *
 */
Region groupGetClippingRegion(CompWindow *w)
{
	Region clip = XCreateRegion();
	CompWindow *cw;
	for(cw = w->next; cw; cw = cw->next)
	{
		if (!cw->invisible && !(cw->state & (CompWindowStateHiddenMask | CompWindowStateOffscreenMask))) {
			Region buf = XCreateRegion();
			
			XRectangle rect;
			rect.x = WIN_REAL_X(cw);
			rect.y = WIN_REAL_Y(cw);
			rect.width = WIN_REAL_WIDTH(cw);
			rect.height = WIN_REAL_HEIGHT(cw);
			XUnionRectWithRegion(&rect, buf, buf);

			XUnionRegion(clip, buf, clip);
			XDestroyRegion(buf);
		}
	}

	return clip;
}

/*
 * groupTabBarTimeout
 *
 */
static Bool 
groupTabBarTimeout(void *data)
{
	GroupSelection *group = (GroupSelection *) data;

	groupTabSetVisibility(group, FALSE, PERMANENT);

	group->tabBar->timeoutHandle = 0;

	return FALSE;	//This will free the timer.
}

/*
 * groupCheckForVisibleTabBars
 *
 */
void
groupCheckForVisibleTabBars(CompScreen *s)
{
	GroupSelection *group;
	GROUP_SCREEN(s);

	gs->tabBarVisible = FALSE;

	for (group = gs->groups; group; group = group->next) {
		if (group->tabBar && (group->tabBar->state != PaintOff)) {
			gs->tabBarVisible = TRUE;
			break;
		}
	}
}

/*
 * groupTabSetVisibility
 *
 */
void groupTabSetVisibility(GroupSelection *group, Bool visible, unsigned int mask)
{
	// fixes bad crash...
	if (!group || !group->windows || !group->tabBar || !HAS_TOP_WIN(group))
		return;
	
	GroupTabBar *bar = group->tabBar;
	CompWindow *topTab = TOP_TAB(group);
	GROUP_SCREEN(group->screen);
	PaintState oldState;

	oldState = bar->state;

	/* hide tab bars for invisible top windows */
	if ((topTab->state & (CompWindowStateHiddenMask | 
			CompWindowStateOffscreenMask)) || topTab->invisible)
	{
		bar->state = PaintOff;
		groupSwitchTopTabInput(group, TRUE);
	} 
	
	else if (visible && bar->state != PaintPermanentOn && 
			(mask & PERMANENT))
	{
		bar->state = PaintPermanentOn;
		groupSwitchTopTabInput(group, FALSE);
	
	}
	
	else if (visible && 
		(bar->state == PaintOff || bar->state == PaintFadeOut))
	{
		bar->state = PaintFadeIn;	
		groupSwitchTopTabInput(group, FALSE);
 	}
 	
 	else if (!visible && (bar->state != PaintPermanentOn || (mask & PERMANENT)) &&
 		(bar->state == PaintOn || bar->state == PaintPermanentOn || bar->state == PaintFadeIn))
 	{
		bar->state = PaintFadeOut;
		groupSwitchTopTabInput(group, TRUE);
 	}

	if (bar->state != oldState && bar->state != PaintPermanentOn) // FIXME remove that when we have a new state for PaintPermanentFadeIn
		bar->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TIME].value.f * 1000) - 
			     bar->animationTime;

	groupCheckForVisibleTabBars(group->screen);
}

/*
 * groupGetDrawOffsetForSlot
 *
 */
void
groupGetDrawOffsetForSlot(GroupTabBarSlot *slot, int *hoffset, int *voffset)
{
	if (!slot || !slot->window)
		return;

	CompWindow *w = slot->window;

	GROUP_WINDOW(w);
	GROUP_SCREEN(w->screen);

	if (slot != gs->draggedSlot) {
		if (hoffset)
			*hoffset = 0;
		if (voffset)
			*voffset = 0;

		return;
	}

	int vx, vy;
	int oldX = w->serverX;
	int oldY = w->serverY;
	
	if (gw->group) {
		w->serverX = WIN_X(TOP_TAB(gw->group)) + WIN_WIDTH(TOP_TAB(gw->group)) / 2 - WIN_WIDTH(w) / 2;
		w->serverY = WIN_Y(TOP_TAB(gw->group)) + WIN_HEIGHT(TOP_TAB(gw->group)) / 2 - WIN_HEIGHT(w) / 2;
	}

	defaultViewportForWindow(w, &vx, &vy);

	if (hoffset)
		*hoffset = ((w->screen->x - vx) % w->screen->hsize) * w->screen->width;

	if (voffset)
		*voffset = ((w->screen->y - vy) % w->screen->vsize) * w->screen->height;

	w->serverX = oldX;
	w->serverY = oldY;
}

/*
 * groupHandleHoverDetection
 *
 */
void groupHandleHoverDetection(GroupSelection *group)
{
	GROUP_SCREEN(group->screen);
	GroupTabBar *bar = group->tabBar;

	if (!HAS_TOP_WIN(group))
		return;

	CompWindow *topTab = TOP_TAB(group);

	if (bar->state != PaintOff) {	// Tab-bar is visible.
		int mouseX, mouseY;
		Bool mouseOnScreen;
		
		mouseOnScreen = groupGetCurrentMousePosition(group->screen, &mouseX, &mouseY);

		if (mouseOnScreen && !(bar->hoveredSlot && XPointInRegion(bar->hoveredSlot->region, mouseX, mouseY)))
		{
			bar->hoveredSlot = NULL;
		
			Region clip;
			clip = groupGetClippingRegion(topTab);

			GroupTabBarSlot *slot;
			for (slot = bar->slots; slot; slot = slot->next)
			{
				Region reg = XCreateRegion();
				XSubtractRegion(slot->region, clip, reg);

				if (XPointInRegion(reg, mouseX, mouseY))
				{
					bar->hoveredSlot = slot;
					break;
				}

				XDestroyRegion(reg);
			}

			XDestroyRegion(clip);
			
			if ((bar->textLayer->state == PaintFadeIn || bar->textLayer->state == PaintOn) &&
			    bar->hoveredSlot != bar->textSlot)
			{
				bar->textLayer->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000)
												- bar->textLayer->animationTime;
				bar->textLayer->state = PaintFadeOut;
			}
			
			else if (bar->textLayer->state == PaintFadeOut && bar->hoveredSlot == bar->textSlot && bar->hoveredSlot)
			{
				bar->textLayer->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000)
												- bar->textLayer->animationTime;
				bar->textLayer->state = PaintFadeIn;
			}
		}
	}
}

/*
 * groupHandleTabBarFade
 *
 */
void groupHandleTabBarFade(GroupSelection *group, int msSinceLastPaint)
{
	GroupTabBar *bar = group->tabBar;

	if ((bar->state == PaintFadeIn || bar->state == PaintFadeOut) &&
	    bar->animationTime > 0)
	{
		bar->animationTime -= msSinceLastPaint;
		
		if (bar->animationTime < 0)
			bar->animationTime = 0;

		if (bar->animationTime == 0)
		{
			if (bar->state == PaintFadeIn) {
				bar->state = PaintOn;
				groupCheckForVisibleTabBars(group->screen);
			}

			else if (bar->state == PaintFadeOut) {
				bar->state = PaintOff;
				groupCheckForVisibleTabBars(group->screen);
		
				if (bar->textLayer)	{
					// Tab-bar is no longer painted, clean up text animation variables.
					bar->textLayer->animationTime = 0;
					bar->textLayer->state = PaintOff;
					bar->textSlot = bar->hoveredSlot = NULL;
						
					groupRenderWindowTitle(group);
				}
			}
		}
	}
}

/*
 * groupHanldeTextFade
 *
 */
void groupHandleTextFade(GroupSelection *group, int msSinceLastPaint)
{
	GROUP_SCREEN(group->screen);
	GroupTabBar *bar = group->tabBar;
	GroupCairoLayer *textLayer = bar->textLayer;		

	if (!textLayer)
		return;

	if ((textLayer->state == PaintFadeIn || textLayer->state == PaintFadeOut) &&
	    textLayer->animationTime > 0)
	{
		textLayer->animationTime -= msSinceLastPaint;
		
		if (textLayer->animationTime < 0)
			textLayer->animationTime = 0;
			
		if (textLayer->animationTime == 0) {
			if (textLayer->state == PaintFadeIn)
				textLayer->state = PaintOn;
			
			else if (textLayer->state == PaintFadeOut)
				textLayer->state = PaintOff;
		}
	}
		
	if (textLayer->state == PaintOff && bar->hoveredSlot)
	{
		// Start text animation for the new hovered slot.
		bar->textSlot = bar->hoveredSlot;
		textLayer->state = PaintFadeIn;
		textLayer->animationTime =  (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000);
			
		groupRenderWindowTitle(group);
	}
		
	else if (textLayer->state == PaintOff && bar->textSlot)
	{
		// Clean Up.
		bar->textSlot = NULL;
		groupRenderWindowTitle(group);
	}
}

/*
 * groupHandleTabChange
 *
 */
static void
groupHandleTabChange(CompScreen *s, GroupSelection *group)
{
	GROUP_SCREEN(s);

	if (!group || !HAS_TOP_WIN(group) || !group->changeTab)
		return;

	if (screenGrabExist(s, "rotate", "plane", 0))
		return;

	CompWindow* topTab = TOP_TAB(group);

	if(group->tabbingState != PaintOff) 
	{
		// if the previous top-tab window is being removed from the group, move the new top-tab window onscreen.
		if(group->ungroupState == UngroupSingle && group->prevTopTab == NULL)
		{
			gs->queued = TRUE;
			moveWindowOnscreen(topTab);
			moveWindow(topTab, group->oldTopTabCenterX - WIN_X(topTab) - WIN_WIDTH(topTab) / 2, 
				group->oldTopTabCenterY - WIN_Y(topTab) - WIN_HEIGHT(topTab) / 2, 
				TRUE, TRUE); 
			syncWindowPosition(topTab);
			gs->queued = FALSE;
			
			// recalc here is needed (for y value)!
			groupRecalcTabBarPos(group, (group->tabBar->region->extents.x1 + group->tabBar->region->extents.x2) / 2,
				WIN_REAL_X(topTab), 
				WIN_REAL_X(topTab) + WIN_REAL_WIDTH(topTab));
			
			group->prevTopTab = group->topTab;
		}
		
		return;
	}

	gs->queued = TRUE;
	moveWindowOnscreen(topTab);
	moveWindow(topTab, group->oldTopTabCenterX - WIN_X(topTab) - WIN_WIDTH(topTab) / 2, 
		group->oldTopTabCenterY - WIN_Y(topTab) - WIN_HEIGHT(topTab) / 2, 
		TRUE, TRUE); 
	syncWindowPosition(topTab);
	gs->queued = FALSE;

	activateWindow (topTab);
			
	if(group->prevTopTab) {
		//we use only the half time here - the second half will be PaintFadeOut
		group->changeAnimationTime = gs->opt[GROUP_SCREEN_OPTION_CHANGE_ANIMATION_TIME].value.f * 500;
		
		group->changeState = PaintFadeIn;
				
		group->changeTab = FALSE;
	}
	
	else	//No window to do animation with.
	{
		group->prevTopTab = group->topTab;
		group->changeTab = FALSE;
	}
	
	return;
}

/*
 * groupHandleAnimation
 *
 */
static void
groupHandleAnimation(CompScreen *s, GroupSelection *group)
{
	GROUP_SCREEN(s);

	if(group->tabbingState != PaintOff || !HAS_TOP_WIN(group))
		return;

	if (screenGrabExist(s, "rotate", "plane", 0))
		return;

	if(group->changeState == PaintFadeIn && group->changeAnimationTime <= 0) {

		// recalc here is needed (for y value)!
		groupRecalcTabBarPos(group, (group->tabBar->region->extents.x1 + group->tabBar->region->extents.x2) / 2,
			WIN_REAL_X(TOP_TAB(group)), 
			WIN_REAL_X(TOP_TAB(group)) + WIN_REAL_WIDTH(TOP_TAB(group)));

		group->changeAnimationTime = gs->opt[GROUP_SCREEN_OPTION_CHANGE_ANIMATION_TIME].value.f * 500 + group->changeAnimationTime;
		
		group->changeState = PaintFadeOut;
	}
				
	if (group->changeState == PaintFadeOut && group->changeAnimationTime <= 0)
	{				
		if (group->prevTopTab)
			moveWindowOffscreen(PREV_TOP_TAB(group));
			
		group->prevTopTab = group->topTab;
					
		group->changeState = PaintOff;
		group->changeAnimationTime = 0;
					
		if (group->nextTopTab) {
			groupChangeTab(group->nextTopTab, group->nextDirection);
			group->nextTopTab = NULL;
						
			groupHandleTabChange(s, group);
		}
		
		else if (gs->opt[GROUP_SCREEN_OPTION_VISIBILITY_TIME].value.f != 0.0f) {
			groupTabSetVisibility (group, TRUE, PERMANENT | SHOW_BAR_INSTANTLY_MASK);
			
			if (group->tabBar->timeoutHandle)
				compRemoveTimeout(group->tabBar->timeoutHandle);
		
			group->tabBar->timeoutHandle = compAddTimeout (
				gs->opt[GROUP_SCREEN_OPTION_VISIBILITY_TIME].value.f * 1000, 
				groupTabBarTimeout, group);
		}
	}

	return;
}

/*
 * groupHandleTab
 *
 */
static void
groupHandleTab(CompScreen *s, GroupSelection *group)
{
	if (group->tabbingState == PaintOff || group->doTabbing ||
	    !HAS_TOP_WIN(group) || !group->changeTab)
		return;
		
	GroupTabBarSlot *slot;
			
	for(slot = group->tabBar->slots; slot; slot = slot->next) {
		if (!slot->window)
			continue;

		CompWindow *w = slot->window;
		GROUP_WINDOW(w);

		if (slot == group->topTab || !(gw->animateState & FINISHED_ANIMATION) || gw->ungroup)
			continue;

		moveWindowOffscreen(w);
	}

	group->changeTab = FALSE;
	group->prevTopTab = group->topTab;
	
	return;
}

/*
 * groupHandleTabbingAnimation
 *
 */
static void
groupHandleTabbingAnimation(CompScreen *s, GroupSelection *group)
{
	int i;

	if (group->tabbingState == PaintOff || group->doTabbing)
		return;
	
	// Not animated any more.
	group->tabbingState = PaintOff;
	groupSyncWindows(group);

	for(i = 0; i < group->nWins; i++) {
		CompWindow *w = group->windows[i];
		GROUP_WINDOW(w);

		gw->animateState = 0;
	}

	return;
}

/*
 * groupHandleUntab
 *
 */
static void
groupHandleUntab(CompScreen *s, GroupSelection *group)
{
	if (group->tabbingState == PaintOff || !group->doTabbing)
		return;

	if (group->topTab || !group->changeTab)
		return;

	groupDeleteTabBar(group);
				
	group->changeAnimationTime = 0;
	group->changeState = PaintOff;
	group->nextTopTab = NULL;

	group->changeTab = FALSE;
	group->prevTopTab = group->topTab;
	
	return;
}

/*
 * groupHandleUngroup
 *
 */
static Bool
groupHandleUngroup(CompScreen *s, GroupSelection *group)
{
	int i;
	GROUP_SCREEN(s);

	if((group->ungroupState == UngroupSingle) && group->doTabbing && group->changeTab)
	{
		for(i = 0; i < group->nWins; i++) {
			CompWindow *w = group->windows[i];
			GROUP_WINDOW(w);
			
			if (gw->ungroup)
			{
				gs->queued = TRUE;
				moveWindowOnscreen(w);
				moveWindow(w, group->oldTopTabCenterX - WIN_X(w) - WIN_WIDTH(w) / 2, 
					group->oldTopTabCenterY - WIN_Y(w) - WIN_HEIGHT(w) / 2, 
					TRUE, TRUE);
				syncWindowPosition(w);
				gs->queued = FALSE;
			}
		}
		
		group->changeTab = FALSE;
		
	}
	
	if ((group->ungroupState == UngroupSingle) && !group->doTabbing) {
		Bool morePending;

		do {
			morePending = FALSE;

			for(i = 0;i < group->nWins; i++) {
				CompWindow *w = group->windows[i];
				GROUP_WINDOW(w);
			
				if (gw->ungroup) {
					groupDeleteGroupWindow(w, TRUE);
					gw->ungroup = FALSE;
					morePending = TRUE;
				}
			}
		} while (morePending);

		group->ungroupState = UngroupNone;
	}

	if (group->prev)
	{
		if ((group->prev->ungroupState == UngroupAll) && !group->prev->doTabbing)
			groupDeleteGroup(group->prev);
	}
	if (!group->next) {
		if ((group->ungroupState == UngroupAll) && !group->doTabbing) {
			groupDeleteGroup(group);
			return FALSE;
		}
	}

	return TRUE;
}

/*
 * groupHandleChanges
 *
 */
void
groupHandleChanges(CompScreen* s)
{
	GROUP_SCREEN(s);

	GroupSelection *group;

	for(group = gs->groups; group; group = group ? group->next : NULL)
	{
		groupHandleUntab(s, group);
		groupHandleTab(s, group);
		groupHandleTabbingAnimation(s, group);
		groupHandleTabChange(s, group);
		groupHandleAnimation(s, group);

		if (!groupHandleUngroup(s, group))
			group = NULL;
	}
}

/* adjust velocity for each animation step (adapted from the scale plugin) */
static int 
adjustTabVelocity(CompWindow * w)
{
	float dx, dy, adjust, amount;
	float x1, y1;

	GROUP_WINDOW(w);

	x1 = y1 = 0.0;

	if (!(gw->animateState & IS_ANIMATED))
		return 0;

	x1 = gw->destination.x;
	y1 = gw->destination.y;

	dx = x1 - (w->serverX + gw->tx);

	adjust = dx * 0.15f;
	amount = fabs(dx) * 1.5f;
	if (amount < 0.5f)
		amount = 0.5f;
	else if (amount > 5.0f)
		amount = 5.0f;

	gw->xVelocity = (amount * gw->xVelocity + adjust) / (amount + 1.0f);

	dy = y1 - (w->serverY + gw->ty);

	adjust = dy * 0.15f;
	amount = fabs(dy) * 1.5f;
	if (amount < 0.5f)
		amount = 0.5f;
	else if (amount > 5.0f)
		amount = 5.0f;

	gw->yVelocity = (amount * gw->yVelocity + adjust) / (amount + 1.0f);

	if (fabs(dx) < 0.1f && fabs(gw->xVelocity) < 0.2f &&
		fabs(dy) < 0.1f && fabs(gw->yVelocity) < 0.2f)
	{
		gw->xVelocity = gw->yVelocity = 0.0f;
		gw->tx = x1 - w->serverX;
		gw->ty = y1 - w->serverY;

		return 0;
	}
	return 1;
}

/*
 * groupDrawTabAnimation
 *
 */
void groupDrawTabAnimation(CompScreen * s, int msSinceLastPaint)
{
	GROUP_SCREEN(s);

	int i;
	
	GroupSelection *group;
	for(group = gs->groups; group; group = group->next)
	{
		if(group->tabbingState == PaintOff)
			continue;

		int steps, dx, dy;
		float amount, chunk;

		amount = msSinceLastPaint * 0.05f * 
		    gs->opt[GROUP_SCREEN_OPTION_TABBING_SPEED].value.f;
		steps = amount / (0.5f * 
			gs->opt[GROUP_SCREEN_OPTION_TABBING_TIMESTEP].value.f);
		if (!steps)
			steps = 1;
		chunk = amount / (float)steps;

		while (steps--) {
			group->doTabbing = FALSE;

			for(i = 0; i < group->nWins; i++) {
				CompWindow *cw = group->windows[i];

				if(!cw)
					continue;

				GROUP_WINDOW(cw);

				if (!(gw->animateState & IS_ANIMATED))
					continue;

				if (!adjustTabVelocity(cw))
				{
					gw->animateState |= FINISHED_ANIMATION;
					gw->animateState &= ~(IS_ANIMATED);
				}

				gw->tx += gw->xVelocity * chunk;
				gw->ty += gw->yVelocity * chunk;

				dx = (cw->serverX + gw->tx) - cw->attrib.x;
				dy = (cw->serverY + gw->ty) - cw->attrib.y;

				group->doTabbing |= (gw->animateState & IS_ANIMATED);

				gs->queued = TRUE;
				moveWindow(cw, dx, dy, FALSE, FALSE);
				gs->queued = FALSE;
			}
			if (!group->doTabbing)
				break;
		}
	}
}

/*
 * groupUpdateTabBars
 *
 */
Bool groupUpdateTabBars(void *display)
{
	CompDisplay *d = (CompDisplay*) display;

	GroupTabBar *bar;
	CompWindow *topTab;
	GroupSelection *group;
	
	Bool titleBarMouseOver;
	Bool tabBarMouseOver;
	
	int mouseX, mouseY;

	CompScreen *s;

	for (s = d->screens; s; s = s->next) {
		if (groupGetCurrentMousePosition(s, &mouseX, &mouseY))
			break;
	}

	if (!s)
		return TRUE;

	GROUP_SCREEN(s);
	
	for(group = gs->groups; group; group = group->next)
	{	
		if(!group->tabBar || !HAS_TOP_WIN(group))
			continue;
		
		topTab = TOP_TAB(group);

		if (topTab->state & (CompWindowStateHiddenMask | CompWindowStateOffscreenMask))
			continue;
		
		if (topTab->invisible)
			continue;

		bar = group->tabBar;

		// create clipping region
		Region clip = groupGetClippingRegion(topTab); 
		
		// title bar
		Region reg = XCreateRegion();
		XRectangle rect;
		rect.x = WIN_X(topTab) - topTab->input.left;
		rect.y = WIN_Y(topTab) - topTab->input.top;
		rect.width = WIN_WIDTH(topTab) + topTab->input.right;
		rect.height = WIN_Y(topTab) - rect.y;
		XUnionRectWithRegion(&rect, reg, reg);

		// clip title bar with stack
		XSubtractRegion(reg, clip, reg);

		titleBarMouseOver = XPointInRegion(reg, mouseX, mouseY);
		XDestroyRegion(reg);

		// clip tab bar with stack
		reg = XCreateRegion();
		XSubtractRegion(bar->region, clip, reg);
		
		if (bar->state != PaintOff && bar->state != PaintFadeOut)
			tabBarMouseOver = XPointInRegion(reg, mouseX, mouseY);
		else
			tabBarMouseOver = FALSE;

		if ((bar->state == PaintOff || bar->state == PaintFadeOut) && titleBarMouseOver) {
			groupRecalcTabBarPos(group, mouseX, WIN_REAL_X(topTab), 
				WIN_REAL_X(topTab) + WIN_REAL_WIDTH(topTab));
			addWindowDamage(topTab);
		}

		XDestroyRegion(reg);
		XDestroyRegion(clip);

		groupTabSetVisibility (group, titleBarMouseOver | tabBarMouseOver, 0);
	}
	
	return TRUE;
}

/*
 * groupGetConstrainRegion
 *
 */
static Region groupGetConstrainRegion(CompScreen *s)
{
	CompWindow *w;
	Region     region;
	REGION     r;
	int        i;
	       
	region = XCreateRegion ();
	if (!region)
		return NULL;

	for (i = 0;i < s->nOutputDev; i++)
		XUnionRegion (&s->outputDev[i].region, region, region);
 	
	r.rects    = &r.extents;
	r.numRects = r.size = 1;
	
	for (w = s->windows; w; w = w->next) {
		if (!w->mapNum)
			 continue;

		if (w->struts) {
			r.extents.x1 = w->struts->top.x;
			r.extents.y1 = w->struts->top.y;
		       	r.extents.x2 = r.extents.x1 + w->struts->top.width;
			r.extents.y2 = r.extents.y1 + w->struts->top.height;
			
			XSubtractRegion (region, &r, region);

			r.extents.x1 = w->struts->bottom.x;
			r.extents.y1 = w->struts->bottom.y;
			r.extents.x2 = r.extents.x1 + w->struts->bottom.width;
			r.extents.y2 = r.extents.y1 + w->struts->bottom.height;
		       
			XSubtractRegion (region, &r, region);

			r.extents.x1 = w->struts->left.x;
			r.extents.y1 = w->struts->left.y;
			r.extents.x2 = r.extents.x1 + w->struts->left.width;
			r.extents.y2 = r.extents.y1 + w->struts->left.height;
		       
			XSubtractRegion (region, &r, region);

			r.extents.x1 = w->struts->right.x;
			r.extents.y1 = w->struts->right.y;
			r.extents.x2 = r.extents.x1 + w->struts->right.width;
			r.extents.y2 = r.extents.y1 + w->struts->right.height;
		       
			XSubtractRegion (region, &r, region);
		}
	}

	return region;
}

/*
 * groupConstrainMovement
 *
 */
static Bool groupConstrainMovement(CompWindow *w, Region constrainRegion, int dx, int dy, 
	int *new_dx, int *new_dy)
{
	GROUP_WINDOW(w);
	int status;
	int origDx = dx, origDy = dy;
	int x, y, width, height;

	if (!gw->group)
		return FALSE;

	if (!dx && !dy)
		return FALSE;

	x = gw->orgPos.x - w->input.left + dx;
	y = gw->orgPos.y - w->input.top + dy;
	width = WIN_REAL_WIDTH(w);
	height = WIN_REAL_HEIGHT(w);

	status = XRectInRegion(constrainRegion, x, y, width, height);

	int xStatus = status;
	while (dx && (xStatus != RectangleIn)) {
		xStatus = XRectInRegion(constrainRegion, x, y - dy,
					width, height);
			
		if (xStatus != RectangleIn)
			dx += (dx < 0) ? 1 : -1;

		x = gw->orgPos.x - w->input.left + dx;
	}

	while (dy && (status != RectangleIn)) {
		status = XRectInRegion(constrainRegion, x, y, 
				width, height);

		if (status != RectangleIn)
			dy += (dy < 0) ? 1 : -1;

		y = gw->orgPos.y - w->input.top + dy;
	}

	if (new_dx)
		*new_dx = dx;

	if (new_dy)
		*new_dy = dy;

	if ((dx != origDx) || (dy != origDy))
		return TRUE;
	else
		return FALSE;
}

/*
 * groupApplyConstrainingToWindows
 *
 */
static void groupApplyConstrainingToWindows(GroupSelection *group, 
	Region constrainRegion, Window constrainedWindow, int dx, int dy)
{
	int i;
	CompWindow *w;

	if (!dx && !dy)
		return;

	for (i = 0; i < group->nWins; i++) {
		w = group->windows[i];
		GROUP_WINDOW(w);

		/* ignore certain windows: we don't want to apply
		   the constraining results on the constrained window
		   itself, not do we want to change the target position
		   of unamimated windows and of windows which
		   already are constrained */
		if (w->id == constrainedWindow)
			continue;

		if(!(gw->animateState & IS_ANIMATED))
			continue;

		if (gw->animateState & DONT_CONSTRAIN)
			continue;

		if (!(gw->animateState & CONSTRAINED_X)) {
			gw->animateState |= IS_ANIMATED;

			/* applying the constraining result of another window
			   might move the window offscreen, too, so check
			   if this is not the case */
			if (groupConstrainMovement(w, constrainRegion, dx, 0, &dx, NULL))
				gw->animateState |= CONSTRAINED_X;

			gw->destination.x += dx;
			gw->orgPos.x += dx;
		}

		if (!(gw->animateState & CONSTRAINED_Y)) {
			gw->animateState |= IS_ANIMATED;

			/* analog to X case */
			if (groupConstrainMovement(w, constrainRegion, 0, dy, NULL, &dy))
				gw->animateState |= CONSTRAINED_Y;

			gw->destination.y += dy;
			gw->orgPos.y += dy;
		}
	}
}

/*
 * groupStartTabbingAnimation
 *
 */
void groupStartTabbingAnimation(GroupSelection *group, Bool tab)
{
	if (!group || (group->tabbingState != PaintOff))
		return;

	CompScreen *s = group->windows[0]->screen;
	int i;
	int dx, dy;
	int constrainStatus;

	group->doTabbing = TRUE;
	group->changeTab = TRUE;

	group->tabbingState = (tab) ? PaintFadeIn : PaintFadeOut;

	if (!tab) {
		/* we need to set up the X/Y constraining on untabbing */
		Region constrainRegion = groupGetConstrainRegion(s);
		Bool constrainedWindows = TRUE;

		if (!constrainRegion)
			return;

		/* reset all flags */
		for (i = 0; i < group->nWins; i++) {
			GROUP_WINDOW(group->windows[i]);
			gw->animateState &= ~(CONSTRAINED_X | CONSTRAINED_Y | DONT_CONSTRAIN);
		}

		/* as we apply the constraining in a flat loop,
		   we may need to run multiple times through this
		   loop until all constraining dependencies are met */
		while (constrainedWindows) {
			constrainedWindows = FALSE;
			/* loop through all windows and try to constrain their
		   	   animation path (going from gw->orgPos to 
		   	   gw->destination) to the active screen area */
			for (i = 0; i < group->nWins; i++) {
				CompWindow *w = group->windows[i];
				GROUP_WINDOW(w);

				/* ignore windows which aren't animated and/or
				   already are at the edge of the screen area */
				if (!(gw->animateState & IS_ANIMATED))
					continue;

				if (gw->animateState & DONT_CONSTRAIN)
					continue;

				/* is the original position inside the screen area? */
				constrainStatus = XRectInRegion(constrainRegion,
						gw->orgPos.x  - w->input.left, 
						gw->orgPos.y - w->input.top,
						WIN_REAL_WIDTH(w), WIN_REAL_HEIGHT(w));

				/* constrain the movement */
				if (groupConstrainMovement(w, constrainRegion,
					    gw->destination.x - gw->orgPos.x,
					    gw->destination.y - gw->orgPos.y, &dx, &dy)) {
					/* handle the case where the window is outside the screen
					   area on its whole animation path */
					if (constrainStatus != RectangleIn && !dx && !dy) {
						gw->animateState |= DONT_CONSTRAIN;
						gw->animateState |= CONSTRAINED_X | CONSTRAINED_Y;

						/* use the original position as last resort */
						gw->destination.x = gw->mainTabOffset.x;
						gw->destination.y = gw->mainTabOffset.y;
					} else {
						/* if we found a valid target position, apply
						   the change also to other windows to retain
						   the distance between the windows */
						groupApplyConstrainingToWindows(group, 
							constrainRegion, w->id, 
							dx - gw->destination.x + gw->orgPos.x, 
							dy - gw->destination.y + gw->orgPos.y);

						/* if we hit constraints, adjust the mask and the 
						   target position accordingly */
		    				if (dx != (gw->destination.x - gw->orgPos.x)) {
							gw->animateState |= CONSTRAINED_X;
							gw->destination.x = gw->orgPos.x + dx;
						}
						
						if (dy != (gw->destination.y - gw->orgPos.y)) {
							gw->animateState |= CONSTRAINED_Y;
							gw->destination.y = gw->orgPos.y + dy;
						}

						constrainedWindows = TRUE;
					}
				}
			}
		}
		XDestroyRegion(constrainRegion);
	}
}

/*
 * groupTabGroup
 *
 */
void groupTabGroup(CompWindow *main)
{
	GROUP_WINDOW(main);
	GROUP_SCREEN(main->screen);
	GroupSelection *group = gw->group;

	if(!group || group->tabBar)
		return;

	groupInitTabBar(group, main);
	groupCreateInputPreventionWindow(group);
	
	group->tabbingState = PaintOff;
	groupChangeTab(gw->slot, RotateUncertain);	//Slot is initialized after groupInitTabBar(group);
	groupRecalcTabBarPos(gw->group, WIN_X(main) + WIN_WIDTH(main)/2,   
 			WIN_X(main), WIN_X(main) + WIN_WIDTH(main)); 
	
	int width, height;
	width = group->tabBar->region->extents.x2 - group->tabBar->region->extents.x1;
	height = group->tabBar->region->extents.y2 - group->tabBar->region->extents.y1;
	
	group->tabBar->textLayer = groupCreateCairoLayer(main->screen, width, height);
	group->tabBar->textLayer->state = PaintOff;
	group->tabBar->textLayer->animationTime = 0;
	groupRenderWindowTitle(group);
	group->tabBar->textLayer->animationTime = gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000;
	group->tabBar->textLayer->state = PaintFadeIn;
	
	group->tabBar->bgLayer = groupCreateCairoLayer(main->screen, width, height);
	group->tabBar->bgLayer->state = PaintOn;
	group->tabBar->bgLayer->animationTime = 0;
	groupRenderTabBarBackground(group);

	width = group->topTab->region->extents.x2 - group->topTab->region->extents.x1;
	height = group->topTab->region->extents.y2 - group->topTab->region->extents.y1;

	group->tabBar->selectionLayer = groupCreateCairoLayer(main->screen, width, height);
	group->tabBar->selectionLayer->state = PaintOn;
	group->tabBar->selectionLayer->animationTime = 0;
	groupRenderTopTabHighlight(group);

	if(!HAS_TOP_WIN(group))
		return;

	GroupTabBarSlot *slot;
					
	for(slot = group->tabBar->slots; slot; slot = slot->next) {		
		CompWindow *cw = slot->window;

		GROUP_WINDOW(cw);
		
		int x = WIN_X(cw), y = WIN_Y(cw);
		
		if(gw->animateState & IS_ANIMATED)
		{
			x = gw->destination.x;
			y = gw->destination.y;
		}
		
		// center the window to the main window
		gw->destination.x = WIN_X(main) + (WIN_WIDTH(main) / 2) - (WIN_WIDTH(cw) / 2);
		gw->destination.y = WIN_Y(main) + (WIN_HEIGHT(main) / 2) - (WIN_HEIGHT(cw) / 2);

		gw->mainTabOffset.x = x - gw->destination.x;	//Distance from destination.
		gw->mainTabOffset.y = y - gw->destination.y;

		gw->orgPos.x = WIN_X(cw);
		gw->orgPos.y = WIN_Y(cw);

		gw->tx = gw->ty = gw->xVelocity = gw->yVelocity = 0.0f;

		gw->animateState |= IS_ANIMATED;
	}

	groupStartTabbingAnimation(group, TRUE);
}

/*
 * groupUntabGroup
 *
 */
void
groupUntabGroup(GroupSelection *group)
{
	if(!HAS_TOP_WIN(group))
		return;

	GROUP_WINDOW(TOP_TAB(group));
	GROUP_SCREEN(TOP_TAB(group)->screen);	
	
	int mainOrgPosX = gw->mainTabOffset.x;
	int mainOrgPosY = gw->mainTabOffset.y;
	int oldX, oldY;
	
	CompWindow* prevTopTab;
	
	if(group->prevTopTab)
		prevTopTab = PREV_TOP_TAB(group);
	else
		prevTopTab = TOP_TAB(group);	//If prevTopTab isn't set, we have no choice but using topTab.
						//It happens when there is still animation, which means the tab wasn't changed anyway.
	
	group->oldTopTabCenterX = WIN_X(prevTopTab) + WIN_WIDTH(prevTopTab)/2;
	group->oldTopTabCenterY = WIN_Y(prevTopTab) + WIN_HEIGHT(prevTopTab)/2;
	
	group->topTab = NULL;

	GroupTabBarSlot *slot;
		
	for(slot = group->tabBar->slots; slot; slot = slot->next) {

		CompWindow *cw = slot->window;

		GROUP_WINDOW(cw);

		gs->queued = TRUE;
		moveWindowOnscreen(cw);
		moveWindow(cw, group->oldTopTabCenterX - WIN_X(cw) - WIN_WIDTH(cw) / 2, 
			group->oldTopTabCenterY - WIN_Y(cw) - WIN_HEIGHT(cw) / 2, 
			TRUE, TRUE);
		syncWindowPosition(cw);
		gs->queued = FALSE;

		/* save the old original position - we might need it if constraining fails */
		oldX = gw->orgPos.x;
		oldY = gw->orgPos.y;

		gw->orgPos.x = group->oldTopTabCenterX - WIN_WIDTH(cw) / 2;
		gw->orgPos.y = group->oldTopTabCenterY - WIN_HEIGHT(cw) / 2;

		gw->destination.x = WIN_X(prevTopTab) + WIN_WIDTH(prevTopTab)/2 - WIN_WIDTH(cw)/2 + gw->mainTabOffset.x - mainOrgPosX;
		gw->destination.y = WIN_Y(prevTopTab) + WIN_HEIGHT(prevTopTab)/2 - WIN_HEIGHT(cw)/2 + gw->mainTabOffset.y - mainOrgPosY;

		gw->mainTabOffset.x = oldX;
		gw->mainTabOffset.y = oldY;

		gw->animateState |= IS_ANIMATED;
		gw->tx = gw->ty = gw->xVelocity = gw->yVelocity = 0.0f;
	}

	group->tabbingState = PaintOff;
	groupStartTabbingAnimation(group, FALSE);

	damageScreen(group->screen);
}

/*
 * groupChangeTab
 *
 */
Bool
groupChangeTab(GroupTabBarSlot* topTab, ChangeTabAnimationDirection direction)
{
	if(!topTab)
		return TRUE;

	CompWindow* w = topTab->window;
	GROUP_WINDOW(w);

	if(!gw->group || gw->group->topTab == topTab || gw->group->tabbingState != PaintOff)
		return TRUE;

	if (gw->group->prevTopTab && gw->group->changeState == PaintOff) {
		gw->group->oldTopTabCenterX = WIN_X(PREV_TOP_TAB(gw->group)) + WIN_WIDTH(PREV_TOP_TAB(gw->group))/2;
		gw->group->oldTopTabCenterY = WIN_Y(PREV_TOP_TAB(gw->group)) + WIN_HEIGHT(PREV_TOP_TAB(gw->group))/2;
	}
	
	if(gw->group->changeState != PaintOff)
		gw->group->nextDirection = direction;
	else if (direction == RotateLeft) 
		gw->group->changeAnimationDirection = 1;
	else if (direction == RotateRight)
		gw->group->changeAnimationDirection = -1;
	else {
		int distanceOld = 0, distanceNew = 0;
		GroupTabBarSlot *slot;

		if (gw->group->topTab)
			for (slot = gw->group->tabBar->slots; slot && (slot != gw->group->topTab); 
				slot = slot->next, distanceOld++);

		for (slot = gw->group->tabBar->slots; slot && (slot != topTab); 
			slot = slot->next, distanceNew++);

		if (distanceNew < distanceOld)
			gw->group->changeAnimationDirection = 1;   //left
		else 
			gw->group->changeAnimationDirection = -1;    //right

		//check if the opposite direction is shorter
		if (abs(distanceNew - distanceOld) > (gw->group->tabBar->nSlots / 2))
			gw->group->changeAnimationDirection *= -1;
	}

	if(gw->group->changeState != PaintOff)
		gw->group->nextTopTab = topTab;
	
	else
	{
		gw->group->topTab = topTab;
		gw->group->changeTab = (gw->group->prevTopTab != topTab);
		
		groupRenderWindowTitle(gw->group);
		groupRenderTopTabHighlight(gw->group);
		addWindowDamage(w);
	}

	return TRUE;
}

/*
 * groupRebuildCairoLayer
 *
 */
GroupCairoLayer* groupRebuildCairoLayer(CompScreen *s, GroupCairoLayer *layer, int width, int height)
{
	int timeBuf = layer->animationTime;
	PaintState stateBuf = layer->state;

	groupDestroyCairoLayer(s, layer);
	layer = groupCreateCairoLayer(s, width, height);

	layer->animationTime = timeBuf;
	layer->state = stateBuf;

	return layer;
}

/*
 * groupClearCairoLayer
 *
 */
void groupClearCairoLayer(GroupCairoLayer *layer)
{
	cairo_t *cr = layer->cairo;
	cairo_save(cr);
	cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
	cairo_paint(cr);
	cairo_restore(cr);
}

/*
 * groupDestroyCairoLayer
 *
 */
void groupDestroyCairoLayer(CompScreen *s, GroupCairoLayer *layer)
{
	if (layer->cairo)
		cairo_destroy(layer->cairo);

	if (layer->surface)
		cairo_surface_destroy(layer->surface);

	if (layer->texBuf)
		free(layer->texBuf);

	if (&layer->texture)
		finiTexture(s, &layer->texture);

	if (layer->pixmap)
		XFreePixmap(s->display->display, layer->pixmap);

	free(layer);
}

/*
 * groupCreateCairoLayer
 *
 */
GroupCairoLayer* groupCreateCairoLayer(CompScreen *s, int width, int height)
{
	GroupCairoLayer* layer = (GroupCairoLayer*) malloc(sizeof(GroupCairoLayer));

	layer->surface	= NULL;
	layer->cairo	= NULL;
	layer->texBuf   = NULL;
	layer->animationTime = 0;
	layer->state = PaintOff;
	layer->pixmap = None;

	initTexture(s, &layer->texture);
	layer->texBuf = calloc(4 * width * height, sizeof (unsigned char));
	layer->surface = cairo_image_surface_create_for_data(layer->texBuf, CAIRO_FORMAT_ARGB32,
														width, height, 4 * width);

	if (cairo_surface_status(layer->surface) != CAIRO_STATUS_SUCCESS) {
		free(layer->texBuf);
		free(layer);
        return NULL;
	}

	layer->cairo = cairo_create(layer->surface);
	if (cairo_status(layer->cairo) != CAIRO_STATUS_SUCCESS) {
		free(layer->texBuf);
		free(layer);
        return NULL;
	}

	groupClearCairoLayer(layer);

	return layer;
}

/*
 * groupRecalcSlotPos
 *
 */
static void 
groupRecalcSlotPos(GroupTabBarSlot *slot, int slotPos)
{
	GROUP_WINDOW (slot->window);
	GROUP_SCREEN (slot->window->screen);
	GroupSelection *group = gw->group;
	XRectangle box;

	if (!HAS_TOP_WIN(group) || !group->tabBar)
		return;

	int border_width = gs->opt[GROUP_SCREEN_OPTION_BORDER_WIDTH].value.i;
	int thumb_size = gs->opt[GROUP_SCREEN_OPTION_THUMB_SIZE].value.i;

	EMPTY_REGION(slot->region);

	box.x = border_width + ((thumb_size + border_width) * slotPos);
	box.y = border_width;

	box.width = thumb_size;
	box.height = thumb_size;

	XUnionRectWithRegion(&box, slot->region, slot->region);

	// in case the name changed we need a new one here
	if(slot->name)
		free(slot->name);
	slot->name = groupGetWindowTitle(slot->window);
}

/*
 * groupRecalcTabBarPos
 *
 */
void groupRecalcTabBarPos(GroupSelection *group, int middleX, int minX1, int maxX2)
{
	if (!HAS_TOP_WIN(group) || !group->tabBar)
		return;

	GroupTabBarSlot *slot;
	GroupTabBar *bar = group->tabBar;
	CompWindow *topTab = TOP_TAB(group);
	Bool isDraggedSlotGroup = FALSE;
	GROUP_SCREEN(group->screen);

	/* first damage the old region to make sure it is updated */
	damageScreenRegion (group->screen, bar->region);

	int border_width = gs->opt[GROUP_SCREEN_OPTION_BORDER_WIDTH].value.i;
	int bar_width;
	int currentSlot;
	XRectangle box;

	// calculate the space which the tabs need
	int tabs_width = 0;
	int tabs_height = 0;
	for(slot = bar->slots; slot; slot = slot->next)
	{
		if (slot == gs->draggedSlot && gs->dragged) {
			isDraggedSlotGroup = TRUE;
			continue;
		}
	
		tabs_width += (slot->region->extents.x2 - slot->region->extents.x1);
		if ((slot->region->extents.y2 - slot->region->extents.y1) > tabs_height)
			tabs_height = slot->region->extents.y2 - slot->region->extents.y1;
	}

	// just a little work-a-round for first call
	int thumb_size = gs->opt[GROUP_SCREEN_OPTION_THUMB_SIZE].value.i;
	if (bar->nSlots && tabs_width <= 0)	{ // first call
		tabs_width = thumb_size * bar->nSlots;
		if (bar->nSlots && tabs_height < thumb_size) // we need to do the standard height too
			tabs_height = thumb_size;
	}

	bar_width = border_width * (bar->nSlots + 1) + tabs_width;

	if (isDraggedSlotGroup)
		bar_width -= border_width;  //1 tab is missing, so we have 1 less border

	if (maxX2 - minX1 < bar_width) 
		box.x = (maxX2 + minX1)/2 - bar_width / 2;	
	else if (middleX - bar_width/2 < minX1)
		box.x = minX1;	
	else if (middleX + bar_width/2 > maxX2)
		box.x = maxX2 - bar_width;	
	else
		box.x = middleX - bar_width / 2;

	box.y = WIN_Y(topTab);
	box.width = bar_width;
	box.height = border_width * 2 + tabs_height;

	EMPTY_REGION(bar->region);
	XUnionRectWithRegion(&box, bar->region, bar->region);
	
	// recalc every slot region
	currentSlot = 0;
	for(slot = bar->slots; slot; slot = slot->next)
	{
		if(slot == gs->draggedSlot && gs->dragged)
			continue;
	
		groupRecalcSlotPos (slot, currentSlot);
		XOffsetRegion (slot->region, 
			bar->region->extents.x1,
			bar->region->extents.y1);
		slot->springX = (slot->region->extents.x1 + slot->region->extents.x2) / 2;
		slot->speed = 0;
		slot->msSinceLastMove = 0;
		
		currentSlot++;
	}
	
	bar->leftSpringX = box.x;
	bar->rightSpringX = box.x + box.width;
	
	bar->rightSpeed = 0;
	bar->leftSpeed = 0;
	
	bar->rightMsSinceLastMove = 0;
	bar->leftMsSinceLastMove = 0;
	
	groupUpdateInputPreventionWindow(group);
	groupRenderTabBarBackground(group);
}

/*
 * groupInsertTabBarSlotBefore
 *
 */
void groupInsertTabBarSlotBefore(GroupTabBar *bar, GroupTabBarSlot *slot, GroupTabBarSlot *nextSlot)
{
	GroupTabBarSlot *prev = nextSlot->prev;

	if(prev) {
		slot->prev = prev;
		prev->next = slot;
	} else {
		bar->slots = slot;
		slot->prev = NULL;
	}

	slot->next = nextSlot;
	nextSlot->prev = slot;
	bar->nSlots++;

	CompWindow *w = slot->window;
	GROUP_WINDOW(w);
	//Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
	//because the tab-bar got wider now, so it will put it in the average between them,
	//which is (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway.
	groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
			     bar->region->extents.x1, bar->region->extents.x2);
}

/*
 * groupInsertTabBarSlotAfter
 *
 */
void groupInsertTabBarSlotAfter(GroupTabBar *bar, GroupTabBarSlot *slot, GroupTabBarSlot *prevSlot)
{
	GroupTabBarSlot *next = prevSlot->next;

	if (next) {
		slot->next = next;
		next->prev = slot;
	} else {
		bar->revSlots = slot;
		slot->next = NULL;
	}

	slot->prev = prevSlot;
	prevSlot->next = slot;
	bar->nSlots++;

	CompWindow *w = slot->window;
	GROUP_WINDOW(w);
	//Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
	//because the tab-bar got wider now, so it will put it in the average between them,
	//which is (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway.
	groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
			     bar->region->extents.x1, bar->region->extents.x2);
}

/*
 * groupInsertTabBarSlot
 *
 */
void groupInsertTabBarSlot(GroupTabBar *bar, GroupTabBarSlot *slot)
{

	if (bar->slots != NULL) {
			bar->revSlots->next = slot;
			slot->prev = bar->revSlots;
			slot->next = NULL;
	} else {
			slot->prev = NULL;
			slot->next = NULL;
			bar->slots = slot;
	}

	bar->revSlots = slot;
	bar->nSlots++;

	CompWindow *w = slot->window;
	GROUP_WINDOW(w);
	//Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
	//because the tab-bar got wider now, so it will put it in the average between them,
	//which is (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway.
	groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
			     bar->region->extents.x1, bar->region->extents.x2);
}

/*
 * groupUnhookTabBarSlot
 *
 */
void groupUnhookTabBarSlot(GroupTabBar *bar, GroupTabBarSlot *slot, Bool temporary)
{
	GroupTabBarSlot *prev = slot->prev;
	GroupTabBarSlot *next = slot->next;
	
	if(prev)
		prev->next = next;
	else
		bar->slots = next;
	
	if(next)
		next->prev = prev;
	else
		bar->revSlots = prev;

	slot->prev = NULL;
	slot->next = NULL;

	bar->nSlots--;

	CompWindow *w = slot->window;
	GROUP_WINDOW(w);
	GROUP_SCREEN(w->screen);

	if (IS_TOP_TAB(w, gw->group) && !temporary)
	{
		if(next)
			groupChangeTab(next, RotateRight);
		else if(prev)
			groupChangeTab(prev, RotateLeft);
		else if(gw->group->nWins == 1)
			gw->group->topTab = NULL;

		if(gs->opt[GROUP_SCREEN_OPTION_UNTAB_ON_CLOSE].value.b)
			groupUntabGroup(gw->group);
	}

	if(IS_PREV_TOP_TAB(w, gw->group) && !temporary)
		gw->group->prevTopTab = NULL;

	if (slot == bar->hoveredSlot)
		bar->hoveredSlot = NULL;
	
	if (slot == bar->textSlot)
	{
		bar->textSlot = NULL;
		
		if (bar->textLayer->state == PaintFadeIn || bar->textLayer->state == PaintOn)
		{
			bar->textLayer->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000)
													- bar->textLayer->animationTime;
			bar->textLayer->state = PaintFadeOut;
		}
	}

	//Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
	//because the tab-bar got thiner now, so (bar->region->extents.x1 + bar->region->extents.x2) / 2
	//Won't cause the new x1 / x2 to be outside the original region.
	groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
			     bar->region->extents.x1, bar->region->extents.x2);
}

/*
 * groupDeleteTabBarSlot
 *
 */
void groupDeleteTabBarSlot(GroupTabBar *bar, GroupTabBarSlot *slot)
{
	groupUnhookTabBarSlot(bar, slot, FALSE);

	if (slot->region)
		XDestroyRegion(slot->region);
	if (slot->name)
		free(slot->name);

	CompWindow *w = slot->window;
	CompScreen *s = w->screen;
	GROUP_WINDOW(w);
	GROUP_SCREEN(s);
		
	if(slot == gs->draggedSlot)
	{
		gs->draggedSlot = NULL;
		gs->dragged = FALSE;

		if (gs->grabState == ScreenGrabTabDrag)
			groupGrabScreen(s, ScreenGrabNone);
	}
	
	gw->slot = NULL;
	free(slot);
}

/*
 * groupCreateSlot
 *
 */
void groupCreateSlot(GroupSelection *group, CompWindow *w)
{
	if(!group->tabBar)
		return;

	GROUP_WINDOW(w);

	GroupTabBarSlot *slot = (GroupTabBarSlot*) malloc(sizeof(GroupTabBarSlot));
	slot->window = w;
	slot->name = groupGetWindowTitle(slot->window);

	slot->region = XCreateRegion();

	groupInsertTabBarSlot(group->tabBar, slot);
	gw->slot = slot;
}

#define SPRING_K	gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_SPRING_K].value.f
#define FRICTION	gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_FRICTION].value.f
#define SIZE		gs->opt[GROUP_SCREEN_OPTION_THUMB_SIZE].value.i
#define BORDER		gs->opt[GROUP_SCREEN_OPTION_BORDER_RADIUS].value.i
#define Y_START_MOVE	gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_Y_DISTANCE].value.i
#define SPEED_LIMIT	gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_SPEED_LIMIT].value.i

/*
 * groupSpringForce
 *
 */
static int groupSpringForce(CompScreen *s, int centerX, int springX)
{
	GROUP_SCREEN(s);
	
	// Each slot has a spring attached to it, starting at springX, and ending at the center of the slot (centerX).
	// The spring will cause the slot to move, using the well-known physical formula F = k * dl... 
	
	return -SPRING_K * (centerX - springX);
}

/*
 * groupDraggedSlotForce
 *
 */
static int groupDraggedSlotForce(CompScreen *s, int distanceX, int distanceY)
{
	// The dragged slot will make the slot move, to get DnD animations (slots will make room for the newly inserted slot).
	// As the dragged slot is closer to the slot, it will put more force on the slot, causing it to make room for the dragged slot...
	// But if the dragged slot gets too close to the slot, they are going to be reordered soon, so the force will get lower.
	
	// If the dragged slot is in the other side of the slot, it will have to make force in the opposite direction.
	
	// So we the needed funtion is an odd function that goes up at first, and down after that.
	// Sinus is a function like that... :)
	
	// The maximum is got when x = (x1 + x2) / 2, in this case: x = SIZE + BORDER.
	// Because of that, for x = SIZE + BORDER, we get a force of SPRING_K * (SIZE + BORDER) / 2.
	// That equals to the force we get from the the spring.
	// This way, the slot won't move when its distance from the dragged slot is SIZE + BORDER (which is the default distance between slots).

	GROUP_SCREEN(s);

	float a = SPRING_K * (SIZE + BORDER) / 2;	// The maximum value.
	float b = PI /  (2 * SIZE + 2 * BORDER);	// This will make distanceX == 2 * (SIZE + BORDER) to get 0, 
							// and distanceX == (SIZE + BORDER) to get the maximum.
	
	// If there is some distance between the slots in the y axis, the slot should get less force...
	// For this, we change max to a lower value, using a simple linear function.
	
	if(distanceY < Y_START_MOVE)
		a *= 1.0f - (float)distanceY / Y_START_MOVE;
	else
		a = 0;
	
	if(abs(distanceX) < 2 * (SIZE + BORDER))
		return a * sin(b * distanceX);
	else
		return 0;
}

/*
 * groupApplyFriction
 *
 */
static void groupApplyFriction(CompScreen *s, int *speed)
{
	GROUP_SCREEN(s);

	if(abs(*speed) < FRICTION)
		*speed = 0;
			
	else if(*speed > 0)
		*speed -= FRICTION;
		
	else if(*speed < 0)
		*speed += FRICTION;
}

/*
 * groupApplySpeedLimit
 *
 */
static void groupApplySpeedLimit(CompScreen *s, int *speed)
{
	GROUP_SCREEN(s);
	
	if(*speed > SPEED_LIMIT)
		*speed = SPEED_LIMIT;
	
	else if(*speed < -SPEED_LIMIT)
		*speed = - SPEED_LIMIT;
}

/*
 * groupApplyForces
 *
 */
void groupApplyForces(CompScreen *s, GroupTabBar *bar, GroupTabBarSlot* draggedSlot)
{
	GROUP_SCREEN(s);

	GroupTabBarSlot* slot, *slot2;
	int centerX, centerY;
	int draggedCenterX, draggedCenterY;

	if (draggedSlot) {
		int vx, vy;
		groupGetDrawOffsetForSlot(draggedSlot, &vx, &vy);

		draggedCenterX = ((draggedSlot->region->extents.x1 + draggedSlot->region->extents.x2) / 2) + vx;
		draggedCenterY = ((draggedSlot->region->extents.y1 + draggedSlot->region->extents.y2) / 2) + vy;
	} else {
		draggedCenterX = 0;
		draggedCenterY = 0;
	}
	
	bar->leftSpeed += groupSpringForce(s, bar->region->extents.x1, bar->leftSpringX);
	bar->rightSpeed += groupSpringForce(s, bar->region->extents.x2, bar->rightSpringX);
	
	if (draggedSlot)
	{
		int leftForce = groupDraggedSlotForce(s, bar->region->extents.x1 - SIZE / 2 - draggedCenterX,
							abs((bar->region->extents.y1 + bar->region->extents.y2) / 2 - draggedCenterY));
		
		int rightForce = groupDraggedSlotForce(s, bar->region->extents.x2 + SIZE / 2 - draggedCenterX,
							abs((bar->region->extents.y1 + bar->region->extents.y2) / 2 - draggedCenterY));
		
		if(leftForce < 0)
			bar->leftSpeed += leftForce;
		if(rightForce > 0)
			bar->rightSpeed += rightForce;
	}
	
	for(slot = bar->slots; slot; slot = slot->next)
	{
		centerX = (slot->region->extents.x1 + slot->region->extents.x2) / 2;
		centerY = (slot->region->extents.y1 + slot->region->extents.y2) / 2;
		
		slot->speed += groupSpringForce(s, centerX, slot->springX);
		
		if(draggedSlot && draggedSlot != slot)
		{		
			int draggedSlotForce = groupDraggedSlotForce(s, centerX - draggedCenterX,
									abs(centerY - draggedCenterY));
			
			slot->speed += draggedSlotForce ;
			
			slot2 = NULL;
			
			if(draggedSlotForce < 0)
			{
				slot2 = slot->prev;
				bar->leftSpeed += draggedSlotForce;
			}
			
			else if(draggedSlotForce > 0)
			{
				slot2 = slot->next;
				bar->rightSpeed += draggedSlotForce;
			}
			
			for( ; slot2; slot2 = (draggedSlotForce < 0)? slot2->prev: slot2->next)
			{
				if(slot2 != draggedSlot)
					slot2->speed += draggedSlotForce;
			}
		}
	}
	
	for(slot = bar->slots; slot; slot = slot->next)
	{
		groupApplyFriction(s, &slot->speed);
		groupApplySpeedLimit(s, &slot->speed);
	}
	
	groupApplyFriction(s, &bar->leftSpeed);
	groupApplySpeedLimit(s, &bar->leftSpeed);
	
	groupApplyFriction(s, &bar->rightSpeed);
	groupApplySpeedLimit(s, &bar->rightSpeed);
}

/*
 * groupApplySpeeds
 *
 */
void groupApplySpeeds(CompScreen* s, GroupTabBar* bar, int msSinceLastRepaint)
{
	GROUP_SCREEN(s);

	GroupTabBarSlot* slot;
	int move;
	XRectangle box;
	Bool updateTabBar = FALSE;
	
	box.x = bar->region->extents.x1;
	box.y = bar->region->extents.y1; 
	box.width = bar->region->extents.x2 - bar->region->extents.x1;
	box.height = bar->region->extents.y2 - bar->region->extents.y1;
	
	bar->leftMsSinceLastMove += msSinceLastRepaint;
	bar->rightMsSinceLastMove += msSinceLastRepaint;
	
	// Left
	move = bar->leftSpeed * bar->leftMsSinceLastMove / 1000;
	
	if(move)
	{
		box.x += move;
		box.width -= move;

		bar->leftMsSinceLastMove = 0;
		updateTabBar = TRUE;
	}
	
	else if(bar->leftSpeed == 0 && bar->region->extents.x1 != bar->leftSpringX && 
		SPRING_K * abs(bar->region->extents.x1 - bar->leftSpringX) < FRICTION)
	{
		// Friction is preventing from the left border to get to its original position.
		box.x += bar->leftSpringX - bar->region->extents.x1;
		box.width -= bar->leftSpringX - bar->region->extents.x1;

		bar->leftMsSinceLastMove = 0;
		updateTabBar = TRUE;
	}
	
	else if(bar->leftSpeed == 0)
		bar->leftMsSinceLastMove = 0;
	
	//Right
	move = bar->rightSpeed * bar->rightMsSinceLastMove / 1000;
	
	if(move)
	{
		box.width += move;
		
		bar->rightMsSinceLastMove = 0;
		updateTabBar = TRUE;
	}
	
	else if(bar->rightSpeed == 0 && bar->region->extents.x2 != bar->rightSpringX && 
		SPRING_K * abs(bar->region->extents.x2 - bar->rightSpringX) < FRICTION)
	{
		// Friction is preventing from the right border to get to its original position.
		box.width += bar->leftSpringX - bar->region->extents.x1;

		bar->leftMsSinceLastMove = 0;
		updateTabBar = TRUE;
	}
	
	else if(bar->rightSpeed == 0)
		bar->rightMsSinceLastMove = 0;
	
	if(updateTabBar)
	{
		EMPTY_REGION(bar->region);
		XUnionRectWithRegion(&box, bar->region, bar->region);
	}
	
	for(slot = bar->slots; slot; slot = slot->next)
	{
		slot->msSinceLastMove += msSinceLastRepaint;
		move = slot->speed * slot->msSinceLastMove / 1000;
		
		if(move)
		{
			XOffsetRegion (slot->region, 
				move, 0);
			slot->msSinceLastMove = 0;
		}
		
		else if(slot->speed == 0 && (slot->region->extents.x1 + slot->region->extents.x2) / 2 != slot->springX &&
			SPRING_K * abs((slot->region->extents.x1 + slot->region->extents.x2) / 2 - slot->springX) < FRICTION)
		{
			// Friction is preventing from the slot to get to its original position.
			
			XOffsetRegion (slot->region,
				slot->springX - (slot->region->extents.x1 + slot->region->extents.x2) / 2, 0);
			slot->msSinceLastMove = 0;
		}
		
		else if(slot->speed == 0)
			slot->msSinceLastMove = 0;
	}
}

/*
 * groupInitTabBar
 *
 */
void groupInitTabBar(GroupSelection *group, CompWindow* topTab)
{
	// error
	if (group->tabBar)
		return;

	GroupTabBar *bar = (GroupTabBar*) malloc(sizeof(GroupTabBar));
	bar->slots = NULL;
	bar->nSlots = 0;
	bar->state = PaintOff;
	bar->animationTime = 0;
	bar->timeoutHandle = 0;
	bar->textLayer = NULL;
	bar->bgLayer = NULL;
	bar->selectionLayer = NULL;
	bar->hoveredSlot = NULL;
	bar->textSlot = NULL;
	group->tabBar = bar;

	bar->region = XCreateRegion();

	int i;
	for(i = 0; i < group->nWins; i++)
		groupCreateSlot(group, group->windows[i]);
	
	groupRecalcTabBarPos(group, WIN_X(topTab) + WIN_WIDTH(topTab) / 2,
				WIN_X(topTab), WIN_X(topTab) + WIN_WIDTH(topTab));
}

/*
 * groupDeleteTabBar
 *
 */
void groupDeleteTabBar(GroupSelection *group)
{
	GroupTabBar *bar = group->tabBar;
	
	groupDestroyCairoLayer(group->screen, bar->textLayer);
	groupDestroyCairoLayer(group->screen, bar->bgLayer);
	groupDestroyCairoLayer(group->screen, bar->selectionLayer);

	groupDestroyInputPreventionWindow(group);

	if (bar->timeoutHandle)
		compRemoveTimeout(bar->timeoutHandle);

	while (bar->slots)
		groupDeleteTabBarSlot(bar, bar->slots);

	if (bar->region)
		XDestroyRegion(bar->region);

	free(bar);
	group->tabBar = NULL;
}

/*
 * groupInitTab
 *
 */
Bool
groupInitTab(CompDisplay * d, CompAction * action, CompActionState state,
	     CompOption * option, int nOption)
{
	CompWindow *w = findWindowAtDisplay(d, d->activeWindow);
	Bool allowUntab = TRUE;
	
	if (!w)
		return TRUE;

	GROUP_WINDOW(w);
	
	if (gw->inSelection)
	{
		groupGroupWindows(d, action, state, option, nOption);
		allowUntab = FALSE;	// If the window was selected, we don't want to untab the group.
					// because the user probably wanted to tab the selected windows.
	}
	
	if (!gw->group)
		return TRUE;
	
	if (gw->group->tabbingState != PaintOff)
		groupSyncWindows(gw->group);

	if (!gw->group->tabBar) {
		groupTabGroup(w);
	} else if (allowUntab) {
		groupUntabGroup(gw->group);
	}

	
	damageScreen(w->screen);
	
	return TRUE;
}

/*
 * groupChangeTabLeft
 *
 */
Bool
groupChangeTabLeft(CompDisplay * d, CompAction * action, CompActionState state,
	       CompOption * option, int nOption)
{
	CompWindow *w = findWindowAtDisplay(d, d->activeWindow);
	CompWindow *topTab = w;
	
	if(!w)
		return TRUE;
	
	GROUP_WINDOW(w);
	GROUP_SCREEN(w->screen);
	
	if(!gw->slot || !gw->group)
		return TRUE;
	
	if(gw->group->nextTopTab)
		topTab = NEXT_TOP_TAB(gw->group);
	else if(gw->group->topTab)
		topTab = TOP_TAB(gw->group);	//If there are no tabbing animations, topTab is never NULL.
	
	gw = GET_GROUP_WINDOW(topTab, gs);

	if(gw->slot->prev)
		return groupChangeTab(gw->slot->prev, RotateLeft);
	else
		return groupChangeTab(gw->group->tabBar->revSlots, RotateLeft);
}

/*
 * groupChangeTabRight
 *
 */
Bool
groupChangeTabRight(CompDisplay * d, CompAction * action, CompActionState state,
	       CompOption * option, int nOption)
{
	CompWindow *w = findWindowAtDisplay(d, d->activeWindow);
	CompWindow *topTab = w;
	
	if(!w)
		return TRUE;
	
	GROUP_WINDOW(w);
	GROUP_SCREEN(w->screen);
	
	if(!gw->slot || !gw->group)
		return TRUE;
	
	if(gw->group->nextTopTab)
		topTab = NEXT_TOP_TAB(gw->group);
	else if(gw->group->topTab)
		topTab = TOP_TAB(gw->group);	//If there are no tabbing animations, topTab is never NULL.
	
	gw = GET_GROUP_WINDOW(topTab, gs);

	if(gw->slot->next)
		return groupChangeTab(gw->slot->next, RotateRight);
	else
		return groupChangeTab(gw->group->tabBar->slots, RotateRight);
}

/*
 * groupSwitchTopTabInput
 *
 */
void
groupSwitchTopTabInput(GroupSelection *group, Bool enable)
{
	if(!group->tabBar || !HAS_TOP_WIN(group))
		return;

	if (!group->inputPrevention)
		groupCreateInputPreventionWindow(group);

	if (!enable) 
		XMapWindow(group->screen->display->display, group->inputPrevention);
	else 
		XUnmapWindow(group->screen->display->display, group->inputPrevention);
}

/*
 * groupUpdateInputPreventionWindow
 *
 */
void
groupUpdateInputPreventionWindow(GroupSelection *group)
{
	XWindowChanges xwc; 
	
	if(!group->tabBar || !HAS_TOP_WIN(group))
		return;

	if (!group->inputPrevention)
		groupCreateInputPreventionWindow(group);

	CompWindow *w = TOP_TAB(group);

	xwc.x = group->tabBar->leftSpringX;
	xwc.y = group->tabBar->region->extents.y1; 
	xwc.width = group->tabBar->rightSpringX - group->tabBar->leftSpringX;
	xwc.height = group->tabBar->region->extents.y2 - group->tabBar->region->extents.y1; 

	xwc.stack_mode = Above;
	
	xwc.sibling = w->id;

	XConfigureWindow(group->screen->display->display, group->inputPrevention, 
			CWSibling | CWStackMode | CWX | CWY | CWWidth | CWHeight, &xwc); 
}

/*
 * groupCreateInputPreventionWindow
 *
 */
void groupCreateInputPreventionWindow(GroupSelection* group)
{
	if (!group->inputPrevention)    //For preventing a memory leak. 
	{ 
		XSetWindowAttributes attrib; 
		attrib.override_redirect = TRUE;
		attrib.event_mask = ButtonPressMask;

		group->inputPrevention = XCreateWindow(group->screen->display->display, 
				group->screen->root, -100, -100, 1, 1, 0, CopyFromParent,  
				InputOnly, CopyFromParent, CWOverrideRedirect | CWEventMask, &attrib); 
	} 
}

/*
 * groupDestroyInputPreventionWindow
 *
 */
void groupDestroyInputPreventionWindow(GroupSelection* group)
{
	if(group->inputPrevention) 
	{ 
		XDestroyWindow(group->screen->display->display, group->inputPrevention); 

		group->inputPrevention = None; 
	}
//
//	if (group->tabBar)
//		group->tabBar->lastHoveredSlot = NULL; 
} 




syntax highlighted by Code2HTML, v. 0.9.1