#include "group.h"

/**
 *
 * Beryl group plugin
 *
 * group.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.
 *
 **/

/* helper for activating a window after a certain time a slot has dragged over it */

static Bool
groupDragHoverTimeout(void* closure)
{
	CompWindow *w = (CompWindow *) closure;

	if (!w)
		return FALSE;

	GROUP_SCREEN(w->screen);

	activateWindow(w);
	gs->dragHoverTimeoutHandle = 0;

	return FALSE;
}

/*
 * groupFindWindowIndex
 *
 */
int
groupFindWindowIndex(CompWindow *w, GroupSelection *g)
{
	int i;

	for(i = 0; i < g->nWins; i++)
	{
		if(g->windows[i]->id == w->id)
			return i;
	}

	return -1;
}

/*
 * groupGrabScreen
 *
 */
void groupGrabScreen(CompScreen * s, GroupScreenGrabState newState)
{
	GROUP_SCREEN(s);

	if ((gs->grabState != newState) && gs->grabIndex) {
		removeScreenGrab(s, gs->grabIndex, NULL);
		gs->grabIndex = 0;
	}

	if (newState == ScreenGrabSelect) {
		gs->grabIndex = pushScreenGrab(s, None, "group");
	} else if (newState == ScreenGrabTabDrag) {
		gs->grabIndex = pushScreenGrab(s, None, "group-drag");
	}

	gs->grabState = newState;
}

/*
 * groupSyncWindows
 *
 */
void groupSyncWindows(GroupSelection *group)
{
       int i;
       for (i = 0; i < group->nWins; i++) {
               CompWindow *w = group->windows[i];
               syncWindowPosition(w);
       }
}

/*
 * groupRaiseWindows
 *
 */
void
groupRaiseWindows(CompWindow * top, GroupSelection *group)
{
	int i;
	for (i = 0; i < group->nWins; i++) {
		CompWindow *w = group->windows[i];
		if (w->id == top->id)
			continue;
		restackWindowBelow(w, top);
	}
}

/*
 * groupMinimizeWindows
 *
 */
static void
groupMinimizeWindows(CompWindow *top, GroupSelection *group, Bool minimize)
{
	int i;
	for (i = 0; i < group->nWins; i++) {
		CompWindow *w = group->windows[i];
		if (w->id == top->id)
			continue;

		if (minimize)
			minimizeWindow(w);
		else
			unminimizeWindow(w);
	}
}

/*
 * groupShadeWindows
 *
 */
static void
groupShadeWindows(CompWindow *top, GroupSelection *group, Bool shade)
{
	int i;
	for (i = 0; i < group->nWins; i++) {
		CompWindow *w = group->windows[i];
		if (w->id == top->id)
			continue;

		if (shade)
			w->state |= CompWindowStateShadedMask;
		else
			w->state &= ~CompWindowStateShadedMask;

		updateWindowAttributes(w, FALSE);
	}
}

/*
 * groupDeleteGroupWindow
 *
 * Note: allowRegroup is need for a special case when groupAddWindowToGroup
 *       calls this function.
 *
 */
void groupDeleteGroupWindow(CompWindow * w, Bool allowRegroup)
{
	GROUP_WINDOW(w);
	GROUP_SCREEN(w->screen);
	GroupSelection *group = gw->group;

	if (!group)
		return;

	if (group->tabBar) {
		if (gw->slot)
		{
			if (gs->draggedSlot && gs->dragged && gs->draggedSlot->window->id == w->id)
				groupUnhookTabBarSlot(group->tabBar, gw->slot, FALSE);
			else 
	        	groupDeleteTabBarSlot(group->tabBar, gw->slot);
		}
		
		if(!gw->ungroup && group->nWins > 1)
		{
			if(HAS_TOP_WIN(group))
			{
				// TODO: maybe add the IS_ANIMATED to the topTab for better constraining...
			
				CompWindow *topTab = TOP_TAB(group);
				GroupWindow *gtw = GET_GROUP_WINDOW(topTab, gs);
				int oldX = gw->orgPos.x;
				int oldY = gw->orgPos.y;
				
				gw->orgPos.x = group->oldTopTabCenterX - WIN_WIDTH(w) / 2;
				gw->orgPos.y = group->oldTopTabCenterY - WIN_HEIGHT(w) / 2;
							    
				gw->destination.x = group->oldTopTabCenterX - WIN_WIDTH(w)/2 + gw->mainTabOffset.x - 
					gtw->mainTabOffset.x; 
				gw->destination.y = group->oldTopTabCenterY - WIN_HEIGHT(w)/2 + gw->mainTabOffset.y - 
					gtw->mainTabOffset.y;

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

				gw->animateState |= IS_ANIMATED;

				gw->tx = gw->ty = gw->xVelocity = gw->yVelocity = 0.0f;
			}
			
			// Although when there is no top-tab, it will never really animate anything,
			// if we don't start the animation, the window will never get remvoed.
			groupStartTabbingAnimation(group, FALSE);

			group->ungroupState = UngroupSingle;
			gw->ungroup = TRUE;
			
			return;
		}
	}

	if (group->nWins && group->windows) {
		CompWindow **buf = group->windows;

		group->windows = (CompWindow **) calloc(group->nWins - 1, sizeof(CompWindow *));

		int counter = 0;
		int i;
		for (i = 0; i < group->nWins; i++) {
			if (buf[i]->id == w->id)
				continue;
			group->windows[counter++] = buf[i];
		}
		group->nWins = counter;

		if (group->nWins == 1)
		{
			damageWindowOutputExtents(group->windows[0]);	// Glow was removed from this window too.
			updateWindowOutputExtents(group->windows[0]);
		}
		
		if (group->nWins == 1 &&
		    gs->opt[GROUP_SCREEN_OPTION_AUTO_UNGROUP].value.b) {
			if (group->changeTab) {
				/* a change animation is pending: this most
				   likely means that a window must be moved
				   back onscreen, so we do that here */
				CompWindow *lw = group->windows[0];

				gs->queued = TRUE;
				moveWindowOnscreen(lw);
				moveWindow(lw, group->oldTopTabCenterX - WIN_X(lw) - WIN_WIDTH(lw) / 2, 
					group->oldTopTabCenterY - WIN_Y(lw) - WIN_HEIGHT(lw) / 2, TRUE, TRUE);
				syncWindowPosition(lw);
				gs->queued = FALSE;
			}
			groupDeleteGroup(group);
		} else if (group->nWins <= 0) {
			free(group->windows);
			group->windows = NULL;
			groupDeleteGroup(group);
		}

		free(buf);

		damageWindowOutputExtents(w);
		gw->group = NULL;
		updateWindowOutputExtents(w);
		
		if (allowRegroup && gs->opt[GROUP_SCREEN_OPTION_AUTOTAB].value.b && (w->type & gs->wMask)) {
			groupAddWindowToGroup(w, NULL);
			groupTabGroup(w);
		}
	}
}

/*
 * groupDeleteGroup
 *
 */
void groupDeleteGroup(GroupSelection *group)
{
	GROUP_SCREEN(group->screen);

	if (group->windows != NULL) {
		if (group->tabBar) {
			groupUntabGroup(group);
			group->ungroupState = UngroupAll;
			return;
		}

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

			damageWindowOutputExtents(cw);
			gw->group = NULL;
			updateWindowOutputExtents(cw);
			
			if (gs->opt[GROUP_SCREEN_OPTION_AUTOTAB].value.b && (cw->type & gs->wMask)) {
				groupAddWindowToGroup(cw, NULL);
				groupTabGroup(cw);
			}
		}
		free(group->windows);
		group->windows = NULL;
	} else if (group->tabBar)
		groupDeleteTabBar(group);

	GroupSelection *prev = group->prev;
	GroupSelection *next = group->next;

	// relink stack
	if (prev || next) {
		if (prev) {
			if (next)
				prev->next = next;
			else {
				prev->next = NULL;
			}
		}
		if (next) {
			if (prev)
				next->prev = prev;
			else {
				next->prev = NULL;
				gs->groups = next;
			}
		}
	} else {
		gs->groups = NULL;
	}
	free(group);
}

/*
 * groupAddWindowToGroup
 *
 */
void
groupAddWindowToGroup(CompWindow * w, GroupSelection *group)
{
	GROUP_SCREEN(w->screen);
	GROUP_WINDOW(w);

	if(group && (gw->group == group))
		return;

	if (gw->group)
	{
		gw->ungroup = TRUE;	//This will prevent setting up animations on the previous group.
		groupDeleteGroupWindow(w, FALSE);
		gw->ungroup = FALSE;
	}
	
	if (group) {
		group->windows = (CompWindow **) realloc(group->windows, sizeof(CompWindow *) * (group->nWins + 1));
		group->windows[group->nWins] = w;
		group->nWins++;

		gw->group = group;
		updateWindowOutputExtents(w);
		
		if(group->nWins == 2)
			updateWindowOutputExtents(group->windows[0]);	// First window in the group got its glow too...

		if (group->tabBar && group->topTab)
		{
			CompWindow *topTab = TOP_TAB(group);
			
			if(!gw->slot)
				groupCreateSlot(group, w);
		
			gw->destination.x = WIN_X(topTab) + (WIN_WIDTH(topTab) / 2) - (WIN_WIDTH(w) / 2);
			gw->destination.y = WIN_Y(topTab) + (WIN_HEIGHT(topTab) / 2) - (WIN_HEIGHT(w) / 2);
			gw->mainTabOffset.x = WIN_X(w) - gw->destination.x;
			gw->mainTabOffset.y = WIN_Y(w) - gw->destination.y;
			gw->orgPos.x = WIN_X(w);
			gw->orgPos.y = WIN_Y(w);

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

			groupStartTabbingAnimation(group, TRUE);
			
			damageScreen(w->screen);
		}
	} else {
		GroupSelection *g = malloc(sizeof(GroupSelection));

		g->windows = (CompWindow **) calloc(1, sizeof(CompWindow *));
		g->windows[0] = w;
		g->screen = w->screen;
		g->nWins = 1;
		g->topTab = NULL;
		g->prevTopTab = NULL;
		g->nextTopTab = NULL;
		g->activateTab = NULL;
		g->doTabbing = FALSE;
		g->changeAnimationTime = 0;
		g->changeAnimationDirection = 0;
		g->changeState = PaintOff;
		g->tabbingState = PaintOff;
		g->changeTab = FALSE;
		g->ungroupState = UngroupNone;
		g->tabBar = NULL;

		g->grabWindow = None;
		g->grabMask = 0;
		
		g->inputPrevention = None;

		g->oldTopTabCenterX = 0;
		g->oldTopTabCenterY = 0;

		// glow color
		srand(time(NULL));
		g->color[0] = rand() % 0xFFFF;
		g->color[1] = rand() % 0xFFFF;
		g->color[2] = rand() % 0xFFFF;
		g->color[3] = 0xFFFF;

		// relink stack
		if (gs->groups) {
			gs->groups->prev = g;
			g->next = gs->groups;
			g->prev = NULL;
			gs->groups = g;
		} else {
			g->prev = NULL;
			g->next = NULL;
			gs->groups = g;
		}

		gw->group = g;
	}
}

/*
 * groupGroupWindows
 *
 */
Bool
groupGroupWindows(CompDisplay * d, CompAction * action,
		  CompActionState state, CompOption * option, int nOption)
{
	GROUP_DISPLAY(d);

	if (gd->tmpSel.nWins > 0) {
		int i;
		CompWindow *cw;
		GroupSelection *group = NULL;
		Bool tabbed = FALSE;

		for(i = 0; i < gd->tmpSel.nWins; i++)
		{
			cw = gd->tmpSel.windows[i];
			GROUP_WINDOW(cw);

			if (gw->group)
			{
				if(!tabbed || group->tabBar)
					group = gw->group;
				
				if(group->tabBar)
					tabbed = TRUE;
			}
		}

		// we need to do one first to get the pointer of a new group
		cw = gd->tmpSel.windows[0];
		groupAddWindowToGroup(cw, group);

		GROUP_WINDOW (cw);
		gw->inSelection = FALSE;
		damageScreen(cw->screen);
		group = gw->group;

		for (i = 1; i < gd->tmpSel.nWins; i++) {
			cw = gd->tmpSel.windows[i];
			GROUP_WINDOW(cw);

			groupAddWindowToGroup(cw, group);

			gw->inSelection = FALSE;
			damageScreen(cw->screen);
		}

		// exit selection
		free(gd->tmpSel.windows);
		gd->tmpSel.windows = NULL;
		gd->tmpSel.nWins = 0;
	}

	return FALSE;
}

/*
 * groupUnGroupWindows
 *
 */
Bool
groupUnGroupWindows(CompDisplay * d, CompAction * action,
		    CompActionState state, CompOption * option,
		    int nOption)
{
	CompWindow *cw = findWindowAtDisplay(d, d->activeWindow);

	if (!cw)
		return FALSE;

	GROUP_WINDOW(cw);

	if (gw->group) {
		groupDeleteGroup(gw->group);
	}

	return FALSE;
}

/*
 * groupRemoveWindow
 *
 */
Bool
groupRemoveWindow(CompDisplay * d, CompAction * action,
		  CompActionState state, CompOption * option, int nOption)
{
	CompWindow *cw = findWindowAtDisplay(d, d->activeWindow);
	if (!cw)
		return FALSE;
	GROUP_WINDOW(cw);

	if (gw->group)
		groupDeleteGroupWindow(cw, TRUE);

	return FALSE;
}

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

	if (gw->group) {

		int nWins = gw->group->nWins;
		int i;
		for (i = 0; i < nWins; i++) {
			CompWindow *cw = gw->group->windows[i];
			closeWindow(cw, getCurrentTimeFromDisplay(d));
		}
	}

	return FALSE;
}

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

	if (gw->group) {
		srand(time(NULL));
		gw->group->color[0] = rand() % 0xFFFF;
		gw->group->color[1] = rand() % 0xFFFF;
		gw->group->color[2] = rand() % 0xFFFF;

		groupRenderTopTabHighlight(gw->group);

		damageScreen(w->screen);
	}

	return FALSE;
}

/*
 * groupSetIgnore
 *
 */
Bool
groupSetIgnore(CompDisplay * d, CompAction * action, CompActionState state,
	       CompOption * option, int nOption)
{
	GROUP_DISPLAY(d);

	gd->ignoreMode = TRUE;

	if (state & CompActionStateInitKey)
		action->state |= CompActionStateTermKey;

	return FALSE;
}

/*
 * groupUnsetIgnore
 *
 */
Bool
groupUnsetIgnore(CompDisplay * d, CompAction * action,
		 CompActionState state, CompOption * option, int nOption)
{
	GROUP_DISPLAY(d);

	gd->ignoreMode = FALSE;

	action->state &= ~CompActionStateTermKey;

	return FALSE;
}

/*
 * groupHandleButtonPressEvent
 *
 */
static void
groupHandleButtonPressEvent(CompDisplay *d, XEvent *event)
{
	int xRoot = event->xbutton.x_root;
	int yRoot = event->xbutton.y_root;
	int button = event->xbutton.button;

	CompScreen *s = findScreenAtDisplay(d, event->xbutton.root);

	if (!s)
		return;

	GROUP_SCREEN(s);

	if (button == 1) {
		GroupSelection *group;
			
		for (group = gs->groups; group; group = group->next) {
			if ((group->inputPrevention == event->xbutton.window) && group->tabBar) {
				GroupTabBarSlot *slot;
					
				for (slot = group->tabBar->slots; slot; slot = slot->next) {
					if (XPointInRegion (slot->region, xRoot, yRoot)) {
						CompScreen *s = group->screen;
						GROUP_SCREEN(s);

						gs->draggedSlot = slot;
						gs->dragged = FALSE; // The slot isn't dragged yet...
							
						gs->prevX = xRoot;
						gs->prevY = yRoot;

						if (gs->grabState == ScreenGrabNone) {
							if (!otherScreenGrabExist(s, "group", "group-drag"))
								groupGrabScreen(s, ScreenGrabTabDrag);
						}
					}
				}
			}
		}
	} else if ((button == 4) || (button == 5)) {
		CompWindow *w = findWindowAtDisplay(d, d->activeWindow);
		CompWindow *topTab = w;
			
		if(!w)
			return;

		GROUP_WINDOW(w);
		GROUP_SCREEN(w->screen);
	
		if(!gw->slot || !gw->group || !gw->group->tabBar)
			return;

		if(gw->group->tabBar->state != PaintOn && gw->group->tabBar->state != PaintPermanentOn)
			return;

		if(!XPointInRegion(gw->group->tabBar->region, xRoot, yRoot))
			return;
			
		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 (button == 4) {
			if(gw->slot->prev)
				groupChangeTab(gw->slot->prev, RotateLeft);
			else
				groupChangeTab(gw->group->tabBar->revSlots, RotateLeft);
		} else {
			if(gw->slot->next)
				groupChangeTab(gw->slot->next, RotateRight);
			else
				groupChangeTab(gw->group->tabBar->slots, RotateRight);
		}
	}
}

/*
 * groupHandleButtonReleaseEvent
 *
 */
static void
groupHandleButtonReleaseEvent(CompDisplay *d, XEvent *event)
{
	GroupSelection *group;

	if (event->xbutton.button != 1)
		return;

	CompScreen *s = findScreenAtDisplay(d, event->xbutton.root);
	if (!s) 
		return;
	
	GROUP_SCREEN(s);

	if(!gs->draggedSlot)
		return;
			
	if(!gs->dragged) {
		groupChangeTab(gs->draggedSlot, RotateUncertain);
		gs->draggedSlot = NULL;

		if (gs->grabState == ScreenGrabTabDrag)
			groupGrabScreen(s, ScreenGrabNone);
				
		return;
	}
			
	GROUP_WINDOW(gs->draggedSlot->window);

	int vx, vy;
	Region newRegion = XCreateRegion();
	XUnionRegion(newRegion, gs->draggedSlot->region, newRegion);

	groupGetDrawOffsetForSlot(gs->draggedSlot, &vx, &vy);
	XOffsetRegion(newRegion, vx, vy);
	
	Bool inserted = FALSE;
	for(group = gs->groups; group; group = group->next) {
		Bool inTabBar;
		
		if (!group->tabBar)
			continue;

		// create clipping region
		Region clip = groupGetClippingRegion(TOP_TAB(group));
		
		Region buf = XCreateRegion();
		XIntersectRegion(newRegion, group->tabBar->region, buf);
		XSubtractRegion(buf, clip, buf);
		XDestroyRegion(clip);
		
		inTabBar = !XEmptyRegion(buf);
		
		XDestroyRegion(buf);
		
		if(!inTabBar)
			continue;

		GroupTabBarSlot *slot;
		
		for(slot = group->tabBar->slots; slot; slot = slot->next) {
			if(slot == gs->draggedSlot)
				continue;
			
			Region slotRegion = XCreateRegion();
			XRectangle rect;
			Bool inSlot;
			
			if(slot->prev && slot->prev != gs->draggedSlot)
				rect.x = slot->prev->region->extents.x2;
			else if(slot->prev && slot->prev == gs->draggedSlot && gs->draggedSlot->prev)
				rect.x = gs->draggedSlot->prev->region->extents.x2;
			else
				rect.x = group->tabBar->region->extents.x1;
			
			rect.y = slot->region->extents.y1;
			
			if(slot->next && slot->next != gs->draggedSlot)
				rect.width = slot->next->region->extents.x1 - rect.x;
			else if(slot->next && slot->next == gs->draggedSlot && gs->draggedSlot->next)
				rect.width = gs->draggedSlot->next->region->extents.x1 - rect.x;
			else
				rect.width = group->tabBar->region->extents.x2;
			
			rect.height = slot->region->extents.y2 - slot->region->extents.y1;
			
			XUnionRectWithRegion(&rect, slotRegion, slotRegion);
			
			Region buf = XCreateRegion();
			XIntersectRegion(newRegion, slotRegion, buf);
			
			inSlot = !XEmptyRegion(buf);
			
			XDestroyRegion(buf);
			XDestroyRegion(slotRegion);

			if (!inSlot)
				continue;
			
			GroupTabBarSlot *tmpDraggedSlot = gs->draggedSlot;
					
			if(group != gw->group) {
				GroupSelection *tmpGroup = gw->group;
				
				// if the dragged window is not the top tab, move it onscreen
				if (!IS_TOP_TAB(gs->draggedSlot->window, tmpGroup) && tmpGroup->topTab) {
					CompWindow *tw = tmpGroup->topTab->window;

					tmpGroup->oldTopTabCenterX = WIN_X(tw) + WIN_WIDTH(tw) / 2;
					tmpGroup->oldTopTabCenterY = WIN_Y(tw) + WIN_HEIGHT(tw) / 2;

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

				// Change the group.
				groupAddWindowToGroup(gs->draggedSlot->window, group);
			} else
				groupUnhookTabBarSlot(group->tabBar, gs->draggedSlot, TRUE);

			// for re-calculating the tab-bar including the dragged window
			gs->draggedSlot = NULL;
			gs->dragged = FALSE;
			inserted = TRUE;
				
			if((tmpDraggedSlot->region->extents.x1 + tmpDraggedSlot->region->extents.x2 + (2 * vx)) / 2 > 
				    (slot->region->extents.x1 + slot->region->extents.x2) / 2)
				groupInsertTabBarSlotAfter(group->tabBar, tmpDraggedSlot, slot);
			else
				groupInsertTabBarSlotBefore(group->tabBar, tmpDraggedSlot, slot);
			
			// Hide tab-bars.
			GroupSelection *tmpGroup;
			for(tmpGroup = gs->groups; tmpGroup; tmpGroup = tmpGroup->next)
				groupTabSetVisibility(tmpGroup, FALSE, PERMANENT);
			
			break;
		}

		if (inserted)
			break;
	}

	if (newRegion)
		XDestroyRegion(newRegion);

	if (!inserted) {
		CompWindow *draggedSlotWindow = gs->draggedSlot->window;
		GroupSelection *tmpGroup;
		
		for(tmpGroup = gs->groups; tmpGroup; tmpGroup = tmpGroup->next)
			groupTabSetVisibility(tmpGroup, FALSE, PERMANENT);
				
		gs->draggedSlot = NULL;
		gs->dragged = FALSE;

		if(gs->opt[GROUP_SCREEN_OPTION_DND_UNGROUP_WINDOW].value.b)
			groupDeleteGroupWindow(draggedSlotWindow, TRUE);

		else if (gw->group && gw->group->topTab) {
			groupRecalcTabBarPos(gw->group,
				(gw->group->tabBar->region->extents.x1 + gw->group->tabBar->region->extents.x2) / 2,
				gw->group->tabBar->region->extents.x1, gw->group->tabBar->region->extents.x2);
		}
			
		// to remove the painted slot	
		damageScreen(s);
	}

	if (gs->grabState == ScreenGrabTabDrag)
		groupGrabScreen(s, ScreenGrabNone);

	if (gs->dragHoverTimeoutHandle) {
		compRemoveTimeout(gs->dragHoverTimeoutHandle);
		gs->dragHoverTimeoutHandle = 0;
	}
}

/*
 * groupHandleMotionEvent
 *
 */

/* the radius to determine if it was a click or a drag */
#define RADIUS 5

static void 
groupHandleMotionEvent(CompScreen * s, int xRoot, int yRoot)
{
	GROUP_SCREEN(s);

	if (gs->grabState == ScreenGrabTabDrag) {
		int dx, dy;
		REGION reg;
		Region draggedRegion = gs->draggedSlot->region;
								
		reg.rects = &reg.extents;
		reg.numRects = 1;

		dx = xRoot - gs->prevX;
		dy = yRoot - gs->prevY;
				
		if(gs->dragged || abs(dx) > RADIUS || abs(dy) > RADIUS) {
			gs->prevX = xRoot;
			gs->prevY = yRoot;
						
			if(!gs->dragged) {
				GroupSelection *group;
				GROUP_WINDOW(gs->draggedSlot->window);
				
				gs->dragged = TRUE;
					
				for(group = gs->groups; group; group = group->next)
					groupTabSetVisibility(group, TRUE, PERMANENT);
					
				groupRecalcTabBarPos(gw->group, (gw->group->tabBar->region->extents.x1 + gw->group->tabBar->region->extents.x2) / 2,
		    						 gw->group->tabBar->region->extents.x1, gw->group->tabBar->region->extents.x2);
			}
			

			int vx,vy;
			groupGetDrawOffsetForSlot(gs->draggedSlot, &vx, &vy);

			// (border = 5) + (buffer = 2) == (damage buffer = 7)
			//TODO: respect border option.
			reg.extents.x1 = draggedRegion->extents.x1 - 7 + vx;
			reg.extents.y1 = draggedRegion->extents.y1 - 7 + vy;
			reg.extents.x2 = draggedRegion->extents.x2 + 7 + vx;
			reg.extents.y2 = draggedRegion->extents.y2 + 7 + vy;
			damageScreenRegion(s, &reg);
									
			XOffsetRegion (gs->draggedSlot->region, dx, dy);
			gs->draggedSlot->springX = (gs->draggedSlot->region->extents.x1 + gs->draggedSlot->region->extents.x2) / 2;

			reg.extents.x1 = draggedRegion->extents.x1 - 7 + vx;
			reg.extents.y1 = draggedRegion->extents.y1 - 7 + vy;
			reg.extents.x2 = draggedRegion->extents.x2 + 7 + vx;
			reg.extents.y2 = draggedRegion->extents.y2 + 7 + vy;
			damageScreenRegion(s, &reg);
		}
	} else if (gs->grabState == ScreenGrabSelect) {
		groupDamageSelectionRect(s, xRoot, yRoot);
	}
}

/*
 * groupHandleEvent
 *
 */
void groupHandleEvent(CompDisplay * d, XEvent * event)
{
	GROUP_DISPLAY(d);

	switch (event->type) {
	case MotionNotify:
		{
			CompScreen *s = findScreenAtDisplay(d, event->xmotion.root);
			if (s)
				groupHandleMotionEvent(s, d->pointerX, d->pointerY);
			
			break;
		}

	case ButtonPress:
		groupHandleButtonPressEvent(d, event);
		break;

	case ButtonRelease:
		groupHandleButtonReleaseEvent(d, event);
		break;

	case UnmapNotify:
		{
			CompWindow *w = findWindowAtDisplay(d, event->xunmap.window);
			if (!w)
				break;
			GROUP_WINDOW(w);
			GROUP_SCREEN(w->screen);

			if (w->pendingUnmaps) {
				if (w->shaded) {
					gw->windowState = WindowShaded;

					if (gw->group && gs->opt[GROUP_SCREEN_OPTION_SHADE_ALL].value.b)
						groupShadeWindows(w, gw->group, TRUE);
				} else if (w->minimized) {
					gw->windowState = WindowMinimized;

					if (gw->group && gs->opt[GROUP_SCREEN_OPTION_MINIMIZE_ALL].value.b)
						groupMinimizeWindows(w, gw->group, TRUE);
				}
			}
			
			if (gw->group) {
				if (gw->group->tabBar && IS_TOP_TAB(w, gw->group)) {
					/* on unmap of the top tab, hide the tab bar and the
					   input prevention window */
					groupTabSetVisibility(gw->group, FALSE, PERMANENT);
				}
				if (!w->pendingUnmaps) {
					//close event
					gw->ungroup = TRUE;	// This will prevent setting up animations on group.
					groupDeleteGroupWindow(w, FALSE);
					gw->ungroup = FALSE;
					damageScreen(w->screen);
				}
			}
			break;
		}

	case ClientMessage:
		if (event->xclient.message_type == d->winActiveAtom) {
			CompWindow *w = findWindowAtDisplay(d, event->xclient.window);
			if (!w)
				return;

			GROUP_WINDOW(w);

			if (gw->group && gw->group->tabBar && HAS_TOP_WIN(gw->group)) {
				CompWindow *tw = TOP_TAB(gw->group);

				if (w->id != tw->id) {
					/* if a non top-tab has been activated, switch to the
					   top-tab instead - but only if is visible */
					if (tw->shaded) {
						tw->state &= ~CompWindowStateShadedMask;
						updateWindowAttributes(tw, FALSE);
					} else if (tw->minimized)
						unminimizeWindow(tw);

					if (!(tw->state & CompWindowStateHiddenMask)) {
						if (!gw->group->changeTab)
							gw->group->activateTab = gw->slot;
						sendWindowActivationRequest(tw->screen, tw->id);
					}
				}
			}
		}
		break;

	default:
		break;
	}

	UNWRAP(gd, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(gd, d, handleEvent, groupHandleEvent);

	switch (event->type) {
	case PropertyNotify:
		// focus event
		if (event->xproperty.atom == d->winActiveAtom) {
			CompWindow *w = findWindowAtDisplay(d, d->activeWindow);
			if (!w)
				break;

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

			if (gw->group) {
				if (!gw->group->tabBar && !screenGrabExist(w->screen, "scale", 0) &&
						gs->opt[GROUP_SCREEN_OPTION_RAISE].value.b) {
					groupRaiseWindows(w, gw->group);
				}

				if (gw->group->activateTab) {
					groupChangeTab(gw->group->activateTab, RotateUncertain);
					gw->group->activateTab = NULL;
				}
			}
		}
		break;

	case EnterNotify:
		{
			CompWindow *w = findWindowAtDisplay(d, event->xcrossing.window);
			if (!w)
				break;

			GROUP_WINDOW(w);
			if (gw->group) {
				GROUP_SCREEN(w->screen);

				if (gs->draggedSlot && gs->dragged && IS_TOP_TAB(w, gw->group)) {
					int hoverTime = 
						gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_HOVER_TIME].value.f * 1000;
					if (gs->dragHoverTimeoutHandle)
						compRemoveTimeout(gs->dragHoverTimeoutHandle);

					if (hoverTime > 0)
						gs->dragHoverTimeoutHandle = 
							compAddTimeout(hoverTime, groupDragHoverTimeout, w);
				}
			}
		}

	case ConfigureNotify:
		{
			CompWindow *w = findWindowAtDisplay(d, event->xconfigure.window);

			if (!w)
				break;

			GROUP_WINDOW(w);

			if (gw->group && gw->group->tabBar && IS_TOP_TAB(w, gw->group) && gw->group->inputPrevention)
				groupUpdateInputPreventionWindow(gw->group);
		}
		break;


	default:
		break;
	}
}

/*
 * groupGetOutputExtentsForWindow
 *
 */
void
groupGetOutputExtentsForWindow (CompWindow * w, CompWindowExtents * output)
{
	GROUP_SCREEN (w->screen);
	GROUP_WINDOW (w);

	UNWRAP (gs, w->screen, getOutputExtentsForWindow);
	(*w->screen->getOutputExtentsForWindow) (w, output);
	WRAP (gs, w->screen, getOutputExtentsForWindow,
		groupGetOutputExtentsForWindow);

	if (gw->group && gw->group->nWins > 1)
	{
		int glowSize = gs->opt[GROUP_SCREEN_OPTION_GLOW_SIZE].value.i;

		if (glowSize > output->left)
			output->left = glowSize;
		if (glowSize > output->right)
			output->right = glowSize;
		if (glowSize > output->top)
			output->top = glowSize;
		if (glowSize > output->bottom)
			output->bottom = glowSize;
	}
}

/*
 * groupWindowResizeNotify
 *
 */
void groupWindowResizeNotify(CompWindow * w, int dx, int dy, int dwidth,
							 int dheight, Bool preview)
{
	GROUP_SCREEN(w->screen);
	GROUP_DISPLAY(w->screen->display);
	GROUP_WINDOW(w);

	UNWRAP(gs, w->screen, windowResizeNotify);
	(*w->screen->windowResizeNotify) (w, dx, dy, dwidth, dheight, preview);
	WRAP(gs, w->screen, windowResizeNotify, groupWindowResizeNotify);

	if (gw->glowQuads)
		groupComputeGlowQuads (w, &gs->glowTexture.matrix);

	if (!dx && !dy && !dwidth && !dheight)
		return;

	if (preview)
		return;

	if (gw->group && gw->group->tabBar && 
	    (gw->group->tabBar->state != PaintOff) && IS_TOP_TAB(w, gw->group)) {
		groupRecalcTabBarPos(gw->group, w->screen->display->pointerX,
			WIN_X(w), WIN_X(w) + WIN_WIDTH(w));
	}

	if (gw->group && !gd->ignoreMode) {
		if (((gw->lastState & MAXIMIZE_STATE) != (w->state & MAXIMIZE_STATE))
			 && gs->opt[GROUP_SCREEN_OPTION_RESIZE_UNMAXIMIZE].value.b) 
		{
			int i;
			for (i = 0; i < gw->group->nWins; i++) {
				CompWindow *cw = gw->group->windows[i];
				if (!cw)
					continue;

				if (cw->id == w->id)
					continue;

				maximizeWindow(cw, w->state & MAXIMIZE_STATE);
			}
		} else if ((gw->group->grabWindow == w->id) && gs->opt[GROUP_SCREEN_OPTION_RESIZE].value.b) {
			int i;
			for (i = 0; i < gw->group->nWins; i++) {
				CompWindow *cw =  gw->group->windows[i];
				if (!cw)
					continue;
			
				if (cw->state & MAXIMIZE_STATE)
					continue;

				if (cw->id == w->id)
					continue;

				int nx = 0;
				int ny = 0;
	
				if (gs->opt[GROUP_SCREEN_OPTION_RELATIVE_DISTANCE].value.b) {
					int distX = cw->serverX - (w->serverX - dx);
					int distY = cw->serverY - (w->serverY - dy);
					int ndistX = distX * ((float) w->serverWidth / (float) (w->serverWidth - dwidth));
					int ndistY = distY * ((float) w->serverHeight / (float) (w->serverHeight - dheight));
					nx = w->serverX + ndistX;
					ny = w->serverY + ndistY;
				} else {
					nx = cw->serverX + dx;
					ny = cw->serverY + dy;
				}
	
				int nwidth = cw->serverWidth + dwidth;
				int nheight = cw->serverHeight + dheight;

				constrainNewWindowSize(cw, nwidth, nheight, &nwidth, &nheight);

				XWindowChanges xwc;
				
				xwc.x = nx;
				xwc.y = ny;
				xwc.width = nwidth;
				xwc.height = nheight;

				configureXWindow(cw, CWX | CWY | CWWidth | CWHeight, &xwc);
			}
		}
	}
}

/*
 * groupWindowMoveNotify
 *
 */
void
groupWindowMoveNotify(CompWindow * w, int dx, int dy, Bool immediate)
{
	GROUP_SCREEN(w->screen);
	GROUP_DISPLAY(w->screen->display);
	GROUP_WINDOW(w);

	UNWRAP(gs, w->screen, windowMoveNotify);
	(*w->screen->windowMoveNotify) (w, dx, dy, immediate);
	WRAP(gs, w->screen, windowMoveNotify, groupWindowMoveNotify);

	if (gw->glowQuads)
		groupComputeGlowQuads (w, &gs->glowTexture.matrix);

	if(!gw->group || gs->queued)
		return;

	if (w->state & CompWindowStateOffscreenMask)
		return;
	
	Bool viewportChange = screenGrabExist (w->screen, "rotate", 0) &&
		((dx && !(dx % w->screen->width)) || (dy && !(dy % w->screen->height)));

	if (viewportChange && (gw->animateState & IS_ANIMATED)) {
		gw->destination.x += dx;
		gw->destination.y += dy;
	}

	if (gw->group->tabBar && IS_TOP_TAB(w, gw->group)) {
		GroupTabBarSlot *slot;
		GroupTabBar *bar = gw->group->tabBar;

		if(gs->opt[GROUP_SCREEN_OPTION_SPRING_MODEL_ON_MOVE].value.b)
			XOffsetRegion (bar->region, 0, dy);
		else
			XOffsetRegion (bar->region, dx, dy);

		bar->rightSpringX += dx;
		bar->leftSpringX += dx;
			
		for(slot = bar->slots; slot; slot = slot->next) {
			if(gs->opt[GROUP_SCREEN_OPTION_SPRING_MODEL_ON_MOVE].value.b)
				XOffsetRegion (slot->region, 0, dy);
			else
				XOffsetRegion (slot->region, dx, dy);
			slot->springX += dx;
		}
			
		groupUpdateInputPreventionWindow(gw->group);
		
		return;
	}

	if (gw->group->doTabbing || gd->ignoreMode || 
		(gw->group->grabWindow != w->id) ||
	    !screenGrabExist (w->screen, "move", 0) ||
	    !gs->opt[GROUP_SCREEN_OPTION_MOVE].value.b)
		return;

	int i;

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

		if (cw->id == w->id)
			continue;

		GROUP_WINDOW(cw);

		if (cw->state & MAXIMIZE_STATE) {
			if (viewportChange) {
				gw->needsPosSync = TRUE;
				groupEnqueueMoveNotify (cw, -dx, -dy, TRUE);
			}
		} else if (!viewportChange) {
			gw->needsPosSync = TRUE;
			groupEnqueueMoveNotify (cw, dx, dy, FALSE);
		}
	}
}

void
groupWindowGrabNotify(CompWindow * w,
                       int x, int y, unsigned int state, unsigned int mask)
{
	GROUP_SCREEN(w->screen);
	GROUP_DISPLAY(w->screen->display);
	GROUP_WINDOW(w);

	if (gw->group && !gd->ignoreMode && !gs->queued) {
		int i;
		for (i = 0; i < gw->group->nWins; i++) {
			CompWindow *cw = gw->group->windows[i];
			if (!cw)
				continue;
			
			if (cw->id != w->id) 
				groupEnqueueGrabNotify (cw, x, y, state, mask);
		}

		gw->group->grabWindow = w->id;
		gw->group->grabMask = mask;
	}

	UNWRAP(gs, w->screen, windowGrabNotify);
	(*w->screen->windowGrabNotify) (w, x, y, state, mask);
	WRAP(gs, w->screen, windowGrabNotify, groupWindowGrabNotify);
}

void groupWindowUngrabNotify(CompWindow * w)
{
	GROUP_SCREEN(w->screen);
	GROUP_DISPLAY(w->screen->display);
	GROUP_WINDOW(w);

	if (gw->group && !gd->ignoreMode && !gs->queued) {
		int i;

		groupDequeueMoveNotifies(w->screen);

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

			if (cw->id != w->id) {
				GROUP_WINDOW(cw);

				if (gw->needsPosSync) {
					syncWindowPosition(cw);
					gw->needsPosSync = FALSE;
				}
				groupEnqueueUngrabNotify (cw);
			}
		}

		gw->group->grabWindow = None;
		gw->group->grabMask = 0;
	}

	UNWRAP(gs, w->screen, windowUngrabNotify);
	(*w->screen->windowUngrabNotify) (w);
	WRAP(gs, w->screen, windowUngrabNotify, groupWindowUngrabNotify);
}


Bool groupDamageWindowRect(CompWindow * w, Bool initial, BoxPtr rect)
{

	GROUP_SCREEN(w->screen);
	Bool status;

	UNWRAP(gs,w->screen,damageWindowRect);
	status = (*w->screen->damageWindowRect) (w,initial,rect);
	WRAP(gs,w->screen,damageWindowRect,groupDamageWindowRect);

	if (initial) {
		GROUP_WINDOW(w);
		
		if (gs->opt[GROUP_SCREEN_OPTION_AUTOTAB].value.b && (w->type & gs->wMask)) {
			if (!gw->group && (gw->windowState == WindowNormal)) {
				groupAddWindowToGroup(w, NULL);
				groupTabGroup(w);
			}
		}

		if ((gw->windowState == WindowMinimized) && gw->group) {
			if (gs->opt[GROUP_SCREEN_OPTION_MINIMIZE_ALL].value.b)
				groupMinimizeWindows(w, gw->group, FALSE);
		} else if ((gw->windowState == WindowShaded) && gw->group) {
			if (gs->opt[GROUP_SCREEN_OPTION_SHADE_ALL].value.b)
				groupShadeWindows(w, gw->group, FALSE);
		}
		
		gw->windowState = WindowNormal;
	}

	return status;

}

void groupWindowStateChangeNotify(CompWindow *w)
{
	 GROUP_SCREEN(w->screen);
	 GROUP_WINDOW(w);

	 gw->lastState = w->lastState;

	 UNWRAP(gs, w->screen, windowStateChangeNotify);
	 (*w->screen->windowStateChangeNotify) (w);
	 WRAP(gs, w->screen, windowStateChangeNotify, groupWindowStateChangeNotify);
}


syntax highlighted by Code2HTML, v. 0.9.1