All pastes #617454 Raw Edit

Stuff

public text v1 · immutable
·published 2007-07-13 08:03 UTC
rendered paste body
/**
 *
 * Compiz group plugin
 *
 * tab.c
 *
 * Copyright : (C) 2006-2007 by Patrick Niklaus, Roi Cohen, Danny Baumann
 * Authors: Patrick Niklaus <patrick.niklaus@googlemail.com>
 *          Roi Cohen       <roico.beryl@gmail.com>
 *          Danny Baumann   <maniac@opencompositing.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.
 *
 **/

#include "group-internal.h"

/*
 * groupGetCurrentMousePosition
 *
 * Description:
 * Return the current function of the pointer at the given screen.
 * The position is queried trough XQueryPointer directly from the xserver.
 *
 */
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;
}

/*
 * groupGetClippingRegion
 *
 * Description:
 * This function returns a clipping region which is used to clip
 * several events involving window stack such as hover detection
 * in the tab bar or Drag'n'Drop. It creates the clipping region
 * with getting the region of every window above the given window
 * and then adds this region to the clipping region using
 * XUnionRectWithRegion. w->region won't work since it doesn't include
 * the window decoration.
 *
 */
Region groupGetClippingRegion(CompWindow *w)
{
	Region clip = XCreateRegion();
	CompWindow *cw;
	for(cw = w->next; cw; cw = cw->next)
	{
		if (!cw->invisible && !(cw->state & CompWindowStateHiddenMask)) {
			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;
}


/*
 * groupClearWindowInputShape
 *
 */
void
groupClearWindowInputShape(CompWindow *w, GroupWindowHideInfo *hideInfo)
{
	XRectangle *rects;
	int count, ordering;

	rects = XShapeGetRectangles(w->screen->display->display,
			w->id, ShapeInput, &count, &ordering);

	if (count == 0) {
		XFree(rects);
		return;
	}

	/* check if the returned shape exactly matches the window shape -
	   if that is true, the window currently has no set input shape */
	if ((count == 1) &&
		(rects[0].x == -w->serverBorderWidth) &&
		(rects[0].y == -w->serverBorderWidth) &&
		(rects[0].width == (w->serverWidth + w->serverBorderWidth)) &&
		(rects[0].height == (w->serverHeight + w->serverBorderWidth)))
	{
		count = 0;
	}

	if (hideInfo->inputRects)
		XFree(hideInfo->inputRects);

	hideInfo->inputRects = rects;
	hideInfo->nInputRects = count;
	hideInfo->inputRectOrdering = ordering;

	XShapeSelectInput(w->screen->display->display, w->id, NoEventMask);

	XShapeCombineRectangles(w->screen->display->display, w->id,
			ShapeInput, 0, 0, NULL, 0, ShapeSet, 0);

	XShapeSelectInput(w->screen->display->display, w->id, ShapeNotify);
}

/*
 * groupSetWindowVisibility
 *
 */
void
groupSetWindowVisibility(CompWindow *w, Bool visible)
{
	GROUP_WINDOW(w);

	if (!visible && !gw->windowHideInfo)
	{
		gw->windowHideInfo = malloc(sizeof(GroupWindowHideInfo));

		if (!gw->windowHideInfo)
			return;

		gw->windowHideInfo->inputRects = NULL;
		gw->windowHideInfo->nInputRects = 0;
		gw->windowHideInfo->shapeMask = XShapeInputSelected(
				w->screen->display->display, w->id);
		groupClearWindowInputShape(w, gw->windowHideInfo);

 		if (w->frame) {
 			gw->windowHideInfo->frameWindow = w->frame;
 			XUnmapWindow(w->screen->display->display, w->frame);
 		} else
			gw->windowHideInfo->frameWindow = None;

 		gw->windowHideInfo->skipState = w->state &
 					(CompWindowStateSkipPagerMask | CompWindowStateSkipTaskbarMask);

		changeWindowState(w, w->state | CompWindowStateSkipPagerMask |
						  CompWindowStateSkipTaskbarMask);
 	}
	else if (visible && gw->windowHideInfo)
	{
 		if (gw->windowHideInfo->nInputRects)
 		{
 			XShapeCombineRectangles(w->screen->display->display,
 						w->id, ShapeInput, 0, 0,
 						gw->windowHideInfo->inputRects,
 						gw->windowHideInfo->nInputRects,
 						ShapeSet, gw->windowHideInfo->inputRectOrdering);
 		} else {
			XShapeCombineMask(w->screen->display->display, w->id,
					ShapeInput, 0, 0, None, ShapeSet);
		}

		if (gw->windowHideInfo->inputRects)
			XFree(gw->windowHideInfo->inputRects);

		XShapeSelectInput(w->screen->display->display, w->id,
						gw->windowHideInfo->shapeMask);

		if (gw->windowHideInfo->frameWindow) {
			if (w->attrib.map_state != IsUnmapped)
				XMapWindow(w->screen->display->display, w->frame);
		}

		changeWindowState(w, (w->state & ~(CompWindowStateSkipPagerMask |
						  CompWindowStateSkipTaskbarMask)) | gw->windowHideInfo->skipState);

		free(gw->windowHideInfo);
		gw->windowHideInfo = NULL;
	}
}

/*
 * groupTabBarTimeout
 *
 * Description:
 * This function is called when the time expired (== timeout).
 * We use this to realize a delay with the bar hiding after tab change.
 * groupHandleAnimation sets up a timer after the animation has finished.
 * This function itself basically just sets the tab bar to a PaintOff status
 * through calling groupSetTabBarVisibility. The PERMANENT mask allows you to fore
 * a hiding of even PaintPermanentOn tab bars.
 *
 */
static Bool
groupTabBarTimeout(void *data)
{
	GroupSelection *group = (GroupSelection *) data;

	groupTabSetVisibility(group, FALSE, PERMANENT);

	group->tabBar->timeoutHandle = 0;

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

/*
 * groupShowDelayTimeout
 *
 */
static Bool
groupShowDelayTimeout(void *data)
{
	int mouseX, mouseY;

	GroupSelection *group = (GroupSelection *) data;
	CompScreen *s = group->screen;
	GROUP_SCREEN(s);

	if (!HAS_TOP_WIN(group))
	{
		gs->showDelayTimeoutHandle = 0;
		return FALSE;	//This will free the timer.
	}

	CompWindow *topTab = group->topTab->window;

	groupGetCurrentMousePosition(s, &mouseX, &mouseY);

	groupRecalcTabBarPos(group, mouseX, WIN_REAL_X(topTab),
		WIN_REAL_X(topTab) + WIN_REAL_WIDTH(topTab));

	groupTabSetVisibility(group, TRUE, 0);

	gs->showDelayTimeoutHandle = 0;
	return FALSE;	//This will free the timer.
}

/*
 * groupCheckForVisibleTabBars
 *
 * Description:
 * This function is used to update the gs->tabBarVisible
 * attribute which is used in groupPaintOutput to do add
 * a PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_MAS mask to the
 * screen mask. It checks if there are any tab bar's with
 * a PaintState which is no PaintOff on the given screen.
 *
 */
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
 *
 * Description:
 * This function is used to set the visibility of the tab bar.
 * The "visibility" is indicated through the PaintState, which
 * can be PaintOn, PaintOff, PaintFadeIn, PaintFadeOut
 * and PaintPermantOn.
 * Currently the mask paramater is mostely used for the PERMANENT mask.
 * This mask affects how the visible parameter is handled, for example if
 * visibule is set to TRUE and the mask to PERMANENT state it will set
 * PaintPermanentOn state for the tab bar. When visibile is FALSE, mask 0
 * and the current state of the tab bar is PaintPermanentOn it won't do anything
 * because its not strong enough to disable a Permanent-State, for those you need
 * the mask.
 *
 */
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);
	PaintState oldState;

	oldState = bar->state;

	/* hide tab bars for invisible top windows */
	if ((topTab->state & CompWindowStateHiddenMask) || 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 == PaintPermanentOn &&
		 !(mask & PERMANENT))
	{
		bar->state = PaintOn;
	}

	else if (visible &&
		(bar->state == PaintOff || bar->state == PaintFadeOut))
	{
		if (groupGetBarAnimations(group->screen)) {
			bar->bgAnimation = AnimationReflex;
			bar->bgAnimationTime = groupGetReflexTime(group->screen) * 1000.0;
		}
		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 = (groupGetFadeTime(group->screen) * 1000) -
			     bar->animationTime;

	if (bar->state != oldState)
		groupDamageTabBarRegion (group);

	groupCheckForVisibleTabBars(group->screen);
}

/*
 * groupGetDrawOffsetForSlot
 *
 * Description:
 * Its used when the draggedSlot is dragged to another viewport.
 * It calculates a correct offset to the real slot position.
 *
 */
void
groupGetDrawOffsetForSlot(GroupTabBarSlot *slot, int *hoffset, int *voffset)
{
	if (!slot || !slot->window)
		return;

	CompWindow *w = slot->window;
	CompWindow *topTab;

	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 (HAS_TOP_WIN(gw->group))
		topTab = TOP_TAB(gw->group);
	else if (HAS_PREV_TOP_WIN(gw->group))
		topTab = PREV_TOP_TAB(gw->group);
	else
	{
		if (hoffset)
			*hoffset = 0;
		if (voffset)
			*voffset = 0;
		return;
	}

	if (gw->group) {
		w->serverX = WIN_X(topTab) + WIN_WIDTH(topTab) / 2 - WIN_WIDTH(w) / 2;
		w->serverY = WIN_Y(topTab) + WIN_HEIGHT(topTab) / 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;
}

/*
 * groupHandleScreenActions
 *
 * Description:
 * This function handles some actions, which need to be applied
 * on all windows but only once. This is needed for some stuff,
 * which needs to be called on screen initial, but can't be called
 * inside initScreen because it uses a WRAPED function.
 * It's called from groupPreparePaintScreen.
 *
 */
void groupHandleScreenActions(CompScreen *s)
{
	GROUP_SCREEN(s);

	if (!gs->screenActions)
		return;

	CompWindow *w;
	// we need to do it from top to buttom of the stack to avoid problems
	// with a reload of beryl and tabbed static groups. (topTab will always
	// be above the other windows in the group)
	for(w = s->reverseWindows; w; w = w->prev) {
		GROUP_WINDOW(w);

		if (gs->screenActions & CHECK_WINDOW_PROPERTIES)
		{
			Bool tabbed;
			long int id;
			GLushort color[3];

			/* read window property to see if window was grouped
			 before - if it was, regroup */
			if (groupCheckWindowProperty(w, &id, &tabbed, color))
			{
				GroupSelection *group = groupFindGroupByID(w->screen, id);
				groupAddWindowToGroup(w, group, id);
				if (tabbed)
					groupTabGroup(w);

				gw->group->color[0] = color[0];
				gw->group->color[1] = color[1];
				gw->group->color[2] = color[2];
				groupRenderTopTabHighlight(gw->group);
				damageScreen(w->screen);
			}
		}

		if (gs->screenActions & APPLY_AUTO_TABBING)
		{
			if (groupGetAutotabCreate(s) && !w->invisible &&
			    matchEval(groupGetWindowMatch(s), w))
			{
				if (!gw->group && (gw->windowState == WindowNormal)) {
					groupAddWindowToGroup(w, NULL, 0);
					groupTabGroup(w);
				}
			}
		}
	}

	// clear up the pending actions
	if (gs->screenActions & CHECK_WINDOW_PROPERTIES)
		gs->screenActions &= ~CHECK_WINDOW_PROPERTIES;
	if (gs->screenActions & APPLY_AUTO_TABBING)
		gs->screenActions &= ~APPLY_AUTO_TABBING;
}

/*
 * groupHandleHoverDetection
 *
 * Description:
 * This function is called from groupPreparePaintScreen to handle
 * the hover detection. This is needed for the text showing up,
 * when you hover a thumb on the thumb bar.
 *
 */
void groupHandleHoverDetection(GroupSelection *group)
{
	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;

		// first get the current mouse position
		mouseOnScreen = groupGetCurrentMousePosition(group->screen, &mouseX, &mouseY);

		// then check if the mouse is in the last hovered slot -- this saves a lot of CPU usage
		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)
			{
				// We need to clip the slot region with the clip region first.
				// This is needed to respect the window stack, so if a window
				// covers a port of that slot, this part won't be used for in-slot-detection.
				Region reg = XCreateRegion();
				XSubtractRegion(slot->region, clip, reg);

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

				XDestroyRegion(reg);
			}

			XDestroyRegion(clip);

			if (bar->textLayer)
			{
				// trigger a FadeOut of the text
				if ((bar->hoveredSlot != bar->textSlot) &&
			   		(bar->textLayer->state == PaintFadeIn || bar->textLayer->state == PaintOn))
				{
					bar->textLayer->animationTime = (groupGetFadeTextTime(group->screen) * 1000) -
						                            bar->textLayer->animationTime;
					bar->textLayer->state = PaintFadeOut;
				}

				// or trigger a FadeIn of the text
				else if (bar->textLayer->state == PaintFadeOut &&
						 bar->hoveredSlot == bar->textSlot && bar->hoveredSlot)
				{
					bar->textLayer->animationTime = (groupGetFadeTextTime(group->screen) * 1000) -
						                            bar->textLayer->animationTime;
					bar->textLayer->state = PaintFadeIn;
				}
			}
		}
	}
}

/*
 * groupHandleTabBarFade
 *
 * Description:
 * This function is called from groupPreparePaintScreen
 * to handle the tab bar fade. It checks the animationTime and updates it,
 * so we can calculate the alpha of the tab bar in the painting code with it.
 *
 */
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;

		// Fade finished
		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);
				}
			}
		}
	}
}

/*
 * groupHandleTextFade
 *
 * Description:
 * This function is called from groupPreparePaintScreen
 * to handle the text fade. It checks the animationTime and updates it,
 * so we can calculate the alpha of the text in the painting code with it.
 *
 */
void groupHandleTextFade(GroupSelection *group, int msSinceLastPaint)
{
	GroupTabBar *bar = group->tabBar;
	GroupCairoLayer *textLayer = bar->textLayer;

	if (!textLayer)
		return;

	// Fade in progress...
	if ((textLayer->state == PaintFadeIn || textLayer->state == PaintFadeOut) &&
	    textLayer->animationTime > 0)
	{
		textLayer->animationTime -= msSinceLastPaint;

		if (textLayer->animationTime < 0)
			textLayer->animationTime = 0;

		// Fade has finished.
		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 =  (groupGetFadeTextTime(group->screen) * 1000);

		groupRenderWindowTitle(group);
	}

	else if (textLayer->state == PaintOff && bar->textSlot)
	{
		// Clean Up.
		bar->textSlot = NULL;
		groupRenderWindowTitle(group);
	}
}

/*
 * groupHanldeTabBarAnimation
 *
 * Description: Handles the different animations for the tab bar defined in
 * GroupAnimationType. Basically that means this function updates
 * tabBar->animation->time as well as checking if the animation is already
 * finished.
 *
 */
void groupHandleTabBarAnimation(GroupSelection *group, int msSinceLastPaint)
{
	GroupTabBar *bar = group->tabBar;

	if (bar->bgAnimation)
	{
		bar->bgAnimationTime -= msSinceLastPaint;

		if (bar->bgAnimationTime <= 0) {
			bar->bgAnimationTime = 0;
			bar->bgAnimation = 0;

			groupRenderTabBarBackground(group);
		}
	}
}

/*
 * groupTabChangeActivateEvent
 *
 * Description: Creates a compiz event to let other plugins know about
 * the starting and ending point of the tab changing animation
 */
static void
groupTabChangeActivateEvent (CompScreen *s,
		     Bool	activating)
{
    CompOption o[2];

    o[0].type = CompOptionTypeInt;
    o[0].name = "root";
    o[0].value.i = s->root;

    o[1].type = CompOptionTypeBool;
    o[1].name = "active";
    o[1].value.b = activating;

    (*s->display->handleCompizEvent) (s->display, "group", "tabChangeActivate", o, 2);
}

/*
 * groupHandleTabChange
 *
 * Description:
 * This function is also called from groupHandleChanges to handle
 * the tab change. It moved the new topTab on the screen as well as doing
 * the initial set for the tab change animation.
 *
 */
static void
groupHandleTabChange(CompScreen *s, GroupSelection *group)
{
	GROUP_SCREEN(s);

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

	// exit when there is a rotate or plane animation
	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;
			groupSetWindowVisibility(topTab, TRUE);
			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;
	groupSetWindowVisibility(topTab, TRUE);
	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;

	if(group->prevTopTab) {
		//we use only the half time here - the second half will be PaintFadeOut
		group->changeAnimationTime = groupGetChangeAnimationTime(s) * 500;

		groupTabChangeActivateEvent(s, TRUE);

		group->changeState = PaintFadeIn;

		group->changeTab = FALSE;
	}

	else	//No window to do animation with.
	{
		group->prevTopTab = group->topTab;
		group->changeTab = FALSE;
		activateWindow (TOP_TAB(group));
	}

	return;
}

/*
 * groupHandleAnimation
 *
 * Description:
 * This function handles the change animation. It's called
 * from groupHandleChanges. Don't let the changeState
 * confuse you, PaintFadeIn equals with the start of the
 * rotate animation and PaintFadeOut is the end of these
 * animation.
 *
 */
static void
groupHandleAnimation(CompScreen *s, GroupSelection *group)
{
	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 += groupGetChangeAnimationTime(s) * 500;

		if (group->changeAnimationTime <= 0)
			group->changeAnimationTime = 0;

		group->changeState = PaintFadeOut;

		if (HAS_TOP_WIN(group))
			activateWindow (TOP_TAB(group));
	}

	if (group->changeState == PaintFadeOut && group->changeAnimationTime <= 0)
	{
		groupTabChangeActivateEvent(s, FALSE);

		int oldChangeAnimationTime = group->changeAnimationTime;

		if (group->prevTopTab)
			groupSetWindowVisibility(PREV_TOP_TAB(group), FALSE);

		group->prevTopTab = group->topTab;
		group->changeState = PaintOff;

		if (group->nextTopTab) {
			groupChangeTab(group->nextTopTab, group->nextDirection);
			group->nextTopTab = NULL;

			groupHandleTabChange(s, group);

			if (group->changeState == PaintFadeIn)
				group->changeAnimationTime += oldChangeAnimationTime;	// If a new animation was started.
		}

		if (group->changeAnimationTime <= 0)
			group->changeAnimationTime = 0;

		else if (groupGetVisibilityTime(s) != 0.0f && group->changeState == PaintOff) {
			groupTabSetVisibility (group, TRUE, PERMANENT | SHOW_BAR_INSTANTLY_MASK);

			if (group->tabBar->timeoutHandle)
				compRemoveTimeout(group->tabBar->timeoutHandle);

			group->tabBar->timeoutHandle = compAddTimeout (
				groupGetVisibilityTime(s) * 1000, groupTabBarTimeout, group);
		}
	}
}

/*
 * groupHandleTab
 *
 * Description:
 * This functions handes the offscreen moves of the tabs
 * after the animation has finished. It's called from
 * groupHandleChanges.
 *
 */
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;

		groupSetWindowVisibility(w, FALSE);
	}

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

	return;
}

/*
 * groupHandleTabbingAnimation
 *
 * Description:
 * This function handles the end of the tab
 * animation. Actually its just sets some
 * states and sync the window positions with X.
 * It's called from groupHandleChanges.
 *
 */
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
 *
 * Description:
 * This function handles the beginning of the untab
 * animation. It deletes the tab bar and set's
 * group->prevTopTab. It's called from groupHandleChanges.
 *
 */
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
 *
 * Description:
 * This function handles the ungroup animation for tabbed groups.
 * It moved the windows on screen and also it calles groupDeleteGroupWindow
 * when its a "single ungroup", which means only one window gets removed
 * from the group. Another task of this function is to check if the group
 * which is before the given group in the linked list needs to be deleted.
 * This is needed to avoid problems with the linked list.
 * It's called from groupHandleChanges.
 *
 */
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;
				groupSetWindowVisibility(w, TRUE);
				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
 *
 * Description:
 * This function is called from groupPreparePaintScreen to
 * go through all groups and apply the other "handle functions"
 * on them.
 *
 */
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
 *
 * Description:
 * This function is called from groupPreparePaintScreen, to move
 * all the animated windows, with the required animation step.
 * The function goes through all grouped animated windows, calculates
 * the required step using adjustTabVelocity, moves the window,
 * and then checks if the animation is finished for that window.
 *
 */
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 * groupGetTabbingSpeed(s);
		steps = amount / (0.5f * groupGetTabbingTimestep(s));
		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
 *
 * Description:
 * This function is responsible for showing / unshowing the tab-bars,
 * when the title-bars / tab-bars are hovered.
 * The function is called whenever a new window is entered,
 * checks if the entered window is a window frame (and if the title
 * bar part of that frame was hovered) or if it was the input
 * prevention window of a tab bar, and sets tab-bar visibility
 * according to that.
 *
 */
void groupUpdateTabBars(CompScreen *s, Window enteredWin)
{
	GROUP_SCREEN(s);
	CompWindow *w;
	GroupSelection *hoveredGroup = NULL;
	int mouseX = -1, mouseY;

	/* first check if the entered window is a frame */
	for (w = s->windows; w; w = w->next)
	{
		if (w->frame == enteredWin)
			break;
	}

	if (w)
	{
		/* is the window the entered frame belongs to inside
		   a tabbed group? if no, it's not interesting for us */
		GROUP_WINDOW(w);
		if (gw->group && gw->group->tabBar)
		{
			/* it is grouped and tabbed, so now we have to
			   check if we hovered the title bar or the frame */
			if (groupGetCurrentMousePosition(s, &mouseX, &mouseY))
			{
				Region reg = XCreateRegion();
				XRectangle rect;
				rect.x = WIN_X(w) - w->input.left;
				rect.y = WIN_Y(w) - w->input.top;
				rect.width = WIN_WIDTH(w) + w->input.right;
				rect.height = WIN_Y(w) - rect.y;
				XUnionRectWithRegion(&rect, reg, reg);

				if (XPointInRegion(reg, mouseX, mouseY))
					hoveredGroup = gw->group;

				XDestroyRegion(reg);
			}
		}
	}

	/* if we didn't hover a title bar, check if we hovered
	   a tab bar (means: input prevention window) */
	if (!hoveredGroup)
	{
		GroupSelection *group;
		for (group = gs->groups; group; group = group->next)
		{
			if (group->inputPrevention == enteredWin)
			{
				/* only accept it if the IPW is mapped */
				if (group->ipwMapped)
				{
					hoveredGroup = group;
					break;
				}
			}
		}
	}

	/* if we found a hovered a tab bar different than the last one (or
	   left a tab bar), hide the old one */
	if (gs->lastHoveredGroup && (hoveredGroup != gs->lastHoveredGroup))
		groupTabSetVisibility (gs->lastHoveredGroup, FALSE, 0);

	/* if we entered a tab bar (or title bar), show the tab bar */
	if (hoveredGroup && HAS_TOP_WIN(hoveredGroup) && !TOP_TAB(hoveredGroup)->grabbed)
	{
		GroupTabBar *bar = hoveredGroup->tabBar;

		if (bar && ((bar->state == PaintOff) || (bar->state == PaintFadeOut))) {
			int showDelayTime = groupGetTabbarShowDelay(s) * 1000;

			/* Show the tab-bar after a delay, only if the tab-bar wasn't fading out. */
			if (showDelayTime > 0 && (bar->state == PaintOff))
			{
				if (gs->showDelayTimeoutHandle)
					compRemoveTimeout (gs->showDelayTimeoutHandle);
				gs->showDelayTimeoutHandle =
					compAddTimeout(showDelayTime, groupShowDelayTimeout, hoveredGroup);
			}
			else
				groupShowDelayTimeout(hoveredGroup);
		}
	}

	gs->lastHoveredGroup = hoveredGroup;
}

/*
 * 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->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);
	GroupSelection *group = gw->group;

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

	if (!main->screen->display->shapeExtension) {
		compLogMessage (main->screen->display, "group", CompLogLevelError,
						"No X shape extension! Tabbing disabled.");
		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);
	if (group->tabBar->textLayer)
	{
		group->tabBar->textLayer->state = PaintOff;
		group->tabBar->textLayer->animationTime = 0;
		groupRenderWindowTitle(group);
		group->tabBar->textLayer->animationTime = groupGetFadeTextTime(main->screen) * 1000;
		group->tabBar->textLayer->state = PaintFadeIn;
	}

	// we need a buffer for DnD here
	int space = groupGetThumbSpace(main->screen);
	int thumb_size = groupGetThumbSize(main->screen);
	group->tabBar->bgLayer = groupCreateCairoLayer(main->screen, width + space + thumb_size, height);
	if (group->tabBar->bgLayer)
	{
		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);
	if (group->tabBar->selectionLayer)
	{
		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;
		groupSetWindowVisibility(cw, TRUE);
		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);

	GroupSelection *group = gw->group;

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

	if (group->changeState == PaintOff && group->topTab == topTab)
		return TRUE;

	if (group->changeState != PaintOff && group->nextTopTab == topTab)
		return TRUE;

	if (group->prevTopTab && group->changeState == PaintOff) {
		group->oldTopTabCenterX = WIN_X(PREV_TOP_TAB(group)) + WIN_WIDTH(PREV_TOP_TAB(group))/2;
		group->oldTopTabCenterY = WIN_Y(PREV_TOP_TAB(group)) + WIN_HEIGHT(PREV_TOP_TAB(group))/2;
	}

	if (group->changeState != PaintOff)
		group->nextDirection = direction;
	else if (direction == RotateLeft)
		group->changeAnimationDirection = 1;
	else if (direction == RotateRight)
		group->changeAnimationDirection = -1;
	else {
		int distanceOld = 0, distanceNew = 0;
		GroupTabBarSlot *slot;

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

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

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

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

	if (group->changeState != PaintOff)
	{
		if (group->prevTopTab == topTab)
		{
			// Reverse animation.
			GroupTabBarSlot *tmp = group->topTab;
			group->topTab = group->prevTopTab;
			group->prevTopTab = tmp;

			group->changeAnimationDirection *= -1;
			group->changeAnimationTime = groupGetChangeAnimationTime(w->screen) * 500 - group->changeAnimationTime;
			group->changeState = (group->changeState == PaintFadeIn)? PaintFadeOut: PaintFadeIn;

			group->nextTopTab = NULL;
		}

		else
			group->nextTopTab = topTab;
	}

	else
	{
		group->topTab = topTab;
		group->changeTab = (group->prevTopTab != topTab);

		groupRenderWindowTitle(group);
		groupRenderTopTabHighlight(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);
	if (!layer)
		return NULL;

	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);

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

	free(layer);
}

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

	Screen *screen;
	Display *display;
	display = s->display->display;
	screen = ScreenOfDisplay (display, s->screenNum);

	layer->surface	= NULL;
	layer->cairo	= NULL;
	layer->buffer   = NULL;
	layer->animationTime = 0;
	layer->state = PaintOff;
	layer->texWidth = width;
	layer->texHeight = height;

	initTexture(s, &layer->texture);

	layer->buffer = calloc(4 * width * height, sizeof(unsigned char));
	if (!layer->buffer) {
		printf("ERROR: Failed to alloc buffer!\n");
		groupDestroyCairoLayer(s, layer);
		return NULL;
	}

	layer->surface = cairo_image_surface_create_for_data(layer->buffer, CAIRO_FORMAT_ARGB32, width, height, 4*width);
	if (cairo_surface_status(layer->surface) != CAIRO_STATUS_SUCCESS) {
		printf("ERROR: Failed to create surface!\n");
		groupDestroyCairoLayer(s, layer);
        return NULL;
	}

	layer->cairo = cairo_create(layer->surface);
	if (cairo_status(layer->cairo) != CAIRO_STATUS_SUCCESS) {
		printf("ERROR: Failed to create context!\n");
		groupDestroyCairoLayer(s, layer);
        return NULL;
	}

	groupClearCairoLayer(layer);

	return layer;
}

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

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

	int space = groupGetThumbSpace(slot->window->screen);
	int thumb_size = groupGetThumbSize(slot->window->screen);

	EMPTY_REGION(slot->region);

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

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

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

/*
 * 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);

	int space = groupGetThumbSpace(group->screen);
	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 = groupGetThumbSize(group->screen);
	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;

		if(isDraggedSlotGroup)
			tabs_width -= thumb_size;
	}

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

	if (isDraggedSlotGroup)
		bar_width -= space;  //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 = space * 2 + tabs_height;

	groupResizeTabBarRegion (group, &box, TRUE);

	// 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;
}

void
groupDamageTabBarRegion (GroupSelection *group)
{
	REGION reg;

	reg.rects = &reg.extents;
	reg.numRects = 1;

	/* we use 15 pixels as damage buffer here, as there is a 10 pixel wide
	border around the selected slot which also needs to be damaged 
	properly - however the best way would be if slot->region was 
	sized including the border */

#define DAMAGE_BUFFER 20

	reg.extents.x1 = MIN (group->tabBar->region->extents.x1,
			      group->tabBar->slots->region->extents.x1) - DAMAGE_BUFFER;
	reg.extents.y1 = MIN (group->tabBar->region->extents.y1,
			      group->tabBar->slots->region->extents.y1) - DAMAGE_BUFFER;

	reg.extents.x2 = MAX (group->tabBar->region->extents.x2,
			      group->tabBar->revSlots->region->extents.x2) + DAMAGE_BUFFER;
	reg.extents.y2 = MAX (group->tabBar->region->extents.y2,
			      group->tabBar->revSlots->region->extents.y2) + DAMAGE_BUFFER;

	damageScreenRegion (group->screen, &reg);
}

void
groupMoveTabBarRegion (GroupSelection *group, int dx, int dy, Bool syncIPW)
{
	groupDamageTabBarRegion (group);

	XOffsetRegion (group->tabBar->region, dx, dy);

	if (syncIPW)
		XMoveWindow (group->screen->display->display, group->inputPrevention,
					 group->tabBar->leftSpringX, group->tabBar->region->extents.y1);

	groupDamageTabBarRegion (group);
}

void
groupResizeTabBarRegion (GroupSelection *group, XRectangle *box, Bool syncIPW)
{
	int oldWidth;

	groupDamageTabBarRegion (group);

	oldWidth = group->tabBar->region->extents.x2 - group->tabBar->region->extents.x1;

	if (group->tabBar->bgLayer && oldWidth != box->width)
	{
		group->tabBar->bgLayer =
			groupRebuildCairoLayer (group->screen,
						group->tabBar->bgLayer,
						box->width +
							groupGetThumbSpace (group->screen) +
							groupGetThumbSize (group->screen),
						box->height);
	}

	EMPTY_REGION (group->tabBar->region);
	XUnionRectWithRegion(box, group->tabBar->region, group->tabBar->region);

	if (syncIPW)
	{
		XWindowChanges xwc;

		xwc.x = box->x;
		xwc.y = box->y;
		xwc.width = box->width;
		xwc.height = box->height;

		xwc.stack_mode = Above;
		xwc.sibling = HAS_TOP_WIN(group) ? TOP_TAB(group)->id : None;

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

	groupDamageTabBarRegion (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);

	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 (groupGetUntabOnClose(w->screen))
			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)
		{
			if (bar->textLayer->state == PaintFadeIn || bar->textLayer->state == PaintOn)
			{
				bar->textLayer->animationTime = (groupGetFadeTextTime(w->screen) * 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);

	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;
	groupUpdateWindowProperty(w);
	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->region = XCreateRegion();

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

#define SPRING_K	 groupGetDragSpringK(s)
#define FRICTION	 groupGetDragFriction(s)
#define SIZE		 groupGetThumbSize(s)
#define BORDER		 groupGetBorderRadius(s)
#define Y_START_MOVE groupGetDragYDistance(s)
#define SPEED_LIMIT	 groupGetDragSpeedLimit(s)

/*
 * groupSpringForce
 *
 */
static int groupSpringForce(CompScreen *s, int centerX, int springX)
{
	// 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).

	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)
{
	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)
{
	if(*speed > SPEED_LIMIT)
		*speed = SPEED_LIMIT;

	else if(*speed < -SPEED_LIMIT)
		*speed = - SPEED_LIMIT;
}

/*
 * groupApplyForces
 *
 */
void groupApplyForces(CompScreen *s, GroupTabBar *bar, GroupTabBarSlot* draggedSlot)
{
	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, GroupSelection *group, int msSinceLastRepaint)
{
	GroupTabBar *bar = group->tabBar;
	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)
		groupResizeTabBarRegion (group, &box, FALSE);

	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->bgAnimation = AnimationNone;
	bar->bgAnimationTime = 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;
	bar->oldWidth = 0;
	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);

	group->ipwMapped = !enable;
}

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

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

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

		group->inputPrevention = None;
		group->ipwMapped = TRUE;
	}
}