/*
 *
 * Compiz ring switcher plugin
 *
 * ring.c
 *
 * Copyright : (C) 2007 by Danny Baumann
 * E-mail    : maniac@opencompositing.org
 *
 * Based on scale.c and switcher.c:
 * Copyright : (C) 2007 David Reveman
 * E-mail    : davidr@novell.com
 *
 * Rounded corner drawing taken from wall.c:
 * Copyright : (C) 2007 Robert Carr
 * E-mail    : racarr@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.
 *
 */

/* TODO list for this plugin
 * - is there a way to get icons larger than 96x96?
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>

#include <X11/Xatom.h>
#include <X11/cursorfont.h>

#include <compiz.h>
#include <text.h>
#include "ring_options.h"

typedef enum {
    RingStateNone = 0,
    RingStateOut,
    RingStateSwitching,
    RingStateIn
} RingState;

typedef enum {
    RingTypeNormal = 0,
    RingTypeGroup,
    RingTypeAll
} RingType;

static int displayPrivateIndex;

typedef struct _RingSlot {
    int   x, y;            /* thumb center coordinates */
    float scale;           /* size scale (fit to maximal thumb size */
    float depthScale;      /* scale for depth impression */
    float depthBrightness; /* brightness for depth impression */
} RingSlot;

typedef struct _RingDrawSlot {
    CompWindow *w;
    RingSlot   **slot;
} RingDrawSlot;

typedef struct _RingDisplay {
    int		    screenPrivateIndex;
    HandleEventProc handleEvent;
} RingDisplay;

typedef struct _RingScreen {
    int windowPrivateIndex;

    PreparePaintScreenProc preparePaintScreen;
    DonePaintScreenProc    donePaintScreen;
    PaintOutputProc        paintOutput;
    PaintWindowProc        paintWindow;
    DamageWindowRectProc   damageWindowRect;

    int  grabIndex;

    RingState state;
    RingType  type;
    Bool      moreAdjust;
    Bool      rotateAdjust;

    Bool paintingSwitcher;

    int     rotTarget;
    int     rotAdjust;
    GLfloat rVelocity;

    Cursor cursor;

    /* only used for sorting */
    CompWindow   **windows;
    RingDrawSlot *drawSlots;
    int          windowsSize;
    int          nWindows;

    Window clientLeader;
    Window selectedWindow;

    /* text display support */
    CompTexture textTexture;
    Pixmap      textPixmap;
    int         textWidth;
    int         textHeight;

    CompMatch match;
    CompMatch *currentMatch;
} RingScreen;

typedef struct _RingWindow {
    RingSlot *slot;

    GLfloat xVelocity;
    GLfloat yVelocity;
    GLfloat scaleVelocity;

    GLfloat tx;
    GLfloat ty;
    GLfloat scale;
    Bool    adjust;
} RingWindow;

#define PI 3.1415926
#define DIST_ROT (3600 / rs->nWindows)

#define GET_RING_DISPLAY(d)				      \
    ((RingDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define RING_DISPLAY(d)		     \
    RingDisplay *rd = GET_RING_DISPLAY (d)

#define GET_RING_SCREEN(s, rd)					  \
    ((RingScreen *) (s)->privates[(rd)->screenPrivateIndex].ptr)

#define RING_SCREEN(s)							   \
    RingScreen *rs = GET_RING_SCREEN (s, GET_RING_DISPLAY (s->display))

#define GET_RING_WINDOW(w, rs)					  \
    ((RingWindow *) (w)->privates[(rs)->windowPrivateIndex].ptr)

#define RING_WINDOW(w)					       \
    RingWindow *rw = GET_RING_WINDOW  (w,		       \
	             GET_RING_SCREEN  (w->screen,	       \
		     GET_RING_DISPLAY (w->screen->display)))

static Bool
isRingWin (CompWindow *w)
{
    RING_SCREEN (w->screen);

    if (w->attrib.override_redirect)
	return FALSE;

    if (w->wmType & (CompWindowTypeDockMask | CompWindowTypeDesktopMask))
	return FALSE;

    if (!w->mapNum || w->attrib.map_state != IsViewable)
    {
	if (ringGetMinimized (w->screen))
	{
	    if (!w->minimized && !w->inShowDesktopMode && !w->shaded)
		return FALSE;
	}
	else
    	    return FALSE;
    }

    if (rs->type == RingTypeNormal)
    {
	if (!w->mapNum || w->attrib.map_state != IsViewable)
	{
	    if (w->serverX + w->width  <= 0    ||
		w->serverY + w->height <= 0    ||
		w->serverX >= w->screen->width ||
		w->serverY >= w->screen->height)
		return FALSE;
	}
	else
	{
	    if (!(*w->screen->focusWindow) (w))
		return FALSE;
	}
    }
    else if (rs->type == RingTypeGroup &&
	     rs->clientLeader != w->clientLeader &&
	     rs->clientLeader != w->id)
    {
	return FALSE;
    }

    if (w->state & CompWindowStateSkipTaskbarMask)
	return FALSE;

    if (w->state & CompWindowStateShadedMask)
	return FALSE;

    if (!matchEval (rs->currentMatch, w))
	return FALSE;

    return TRUE;
}

static void 
ringFreeWindowTitle (CompScreen *s)
{
    RING_SCREEN(s);

    if (!rs->textPixmap)
	return;

    releasePixmapFromTexture (s, &rs->textTexture);
    initTexture (s, &rs->textTexture);
    XFreePixmap (s->display->display, rs->textPixmap);
    rs->textPixmap = None;
}

static void 
ringRenderWindowTitle (CompScreen *s)
{
    CompTextAttrib tA;
    int            stride;
    void           *data;

    RING_SCREEN (s);

    ringFreeWindowTitle (s);
    if (!ringGetWindowTitle (s))
	return;

    int ox1, ox2, oy1, oy2;
    getCurrentOutputExtents (s, &ox1, &oy1, &ox2, &oy2);

    /* 75% of the output device as maximum width */
    tA.maxwidth = (ox2 - ox1) * 3 / 4;
    tA.maxheight = 100;
    tA.screen = s;
    tA.size = ringGetTitleFontSize (s);
    tA.color[0] = ringGetTitleFontColorRed (s);
    tA.color[1] = ringGetTitleFontColorGreen (s);
    tA.color[2] = ringGetTitleFontColorBlue (s);
    tA.color[3] = ringGetTitleFontColorAlpha (s);
    tA.style = (ringGetTitleFontBold (s)) ?
	       TEXT_STYLE_BOLD : TEXT_STYLE_NORMAL;
    tA.family = "Sans";
    tA.ellipsize = TRUE;

    tA.renderMode = TextRenderWindowTitle;
    tA.data = (void*)rs->selectedWindow;

    initTexture (s, &rs->textTexture);

    if ((*s->display->fileToImage) (s->display, TEXT_ID, (char *)&tA,
			 	    &rs->textWidth, &rs->textHeight,
				    &stride, &data))
    {
	rs->textPixmap = (Pixmap)data;
	bindPixmapToTexture (s, &rs->textTexture, rs->textPixmap,
			     rs->textWidth, rs->textHeight, 32);
    }
    else 
    {
	rs->textPixmap = None;
	rs->textWidth  = 0;
	rs->textHeight = 0;
    }
}

static void
ringDrawWindowTitle (CompScreen *s)
{
    RING_SCREEN(s);
    GLboolean wasBlend;
    GLint oldBlendSrc, oldBlendDst;

    float width = rs->textWidth;
    float height = rs->textHeight;
    float border = 10.0f;

    int ox1, ox2, oy1, oy2;
    getCurrentOutputExtents (s, &ox1, &oy1, &ox2, &oy2);

    float x = ox1 + ((ox2 - ox1) / 2) - (rs->textWidth / 2);
    float y;

    /* assign y (for the lower corner!) according to the setting */
    switch (ringGetTitleTextPlacement (s))
    {
	case TitleTextPlacementCenteredOnScreen:
	    y = oy1 + ((oy2 - oy1) / 2) + (height / 2);
	    break;
	case TitleTextPlacementAboveRing:
	case TitleTextPlacementBelowRing:
	    {
		XRectangle workArea;
		getWorkareaForOutput (s, s->currentOutputDev, &workArea);

	    	if (ringGetTitleTextPlacement (s) == 
		    TitleTextPlacementAboveRing)
    		    y = oy1 + workArea.y + (2 * border) + height;
		else
		    y = oy1 + workArea.y + workArea.height - (2 * border);
	    }
	    break;
	default:
	    return;
	    break;
    }

    x = floor (x);
    y = floor (y);

    glGetIntegerv (GL_BLEND_SRC, &oldBlendSrc);
    glGetIntegerv (GL_BLEND_DST, &oldBlendDst);
    wasBlend = glIsEnabled (GL_BLEND);

    if (!wasBlend)
	glEnable (GL_BLEND);
    glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

    glColor4us (ringGetTitleBackColorRed (s),
		ringGetTitleBackColorGreen (s),
		ringGetTitleBackColorBlue (s),
		ringGetTitleBackColorAlpha (s));

    glPushMatrix ();

    glTranslatef (x, y - height, 0.0f);
    glRectf (0.0f, height, width, 0.0f);
    glRectf (0.0f, 0.0f, width, -border);
    glRectf (0.0f, height + border, width, height);
    glRectf (-border, height, 0.0f, 0.0f);
    glRectf (width, height, width + border, 0.0f);
    glTranslatef (-border, -border, 0.0f);

#define CORNER(a,b) \
    for (k = a; k < b; k++) \
    {\
	float rad = k * (PI / 180.0f);\
	glVertex2f (0.0f, 0.0f);\
	glVertex2f (cos(rad) * border, sin(rad) * border);\
	glVertex2f (cos((k-1) * (PI / 180.0f)) * border, \
		    sin((k-1) * (PI / 180.0f)) * border);\
    }

    /* Rounded corners */
    int k;

    glTranslatef (border, border, 0.0f);
    glBegin (GL_TRIANGLES);
    CORNER (180, 270) glEnd ();
    glTranslatef (-border, -border, 0.0f);

    glTranslatef (width + border, border, 0.0f);
    glBegin (GL_TRIANGLES);
    CORNER (270, 360) glEnd ();
    glTranslatef (-(width + border), -border, 0.0f);

    glTranslatef (border, height + border, 0.0f);
    glBegin (GL_TRIANGLES);
    CORNER (90, 180) glEnd ();
    glTranslatef (-border, -(height + border), 0.0f);

    glTranslatef (width + border, height + border, 0.0f);
    glBegin (GL_TRIANGLES);
    CORNER (0, 90) glEnd ();
    glTranslatef (-(width + border), -(height + border), 0.0f);

    glPopMatrix ();

#undef CORNER

    glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glColor4f (1.0, 1.0, 1.0, 1.0);

    enableTexture (s, &rs->textTexture,COMP_TEXTURE_FILTER_GOOD);

    CompMatrix *m = &rs->textTexture.matrix;

    glBegin (GL_QUADS);

    glTexCoord2f (COMP_TEX_COORD_X (m, 0), COMP_TEX_COORD_Y (m ,0));
    glVertex2f (x, y - height);
    glTexCoord2f (COMP_TEX_COORD_X (m, 0), COMP_TEX_COORD_Y (m, height));
    glVertex2f (x, y);
    glTexCoord2f (COMP_TEX_COORD_X (m, width), COMP_TEX_COORD_Y (m, height));
    glVertex2f (x + width, y);
    glTexCoord2f (COMP_TEX_COORD_X (m, width), COMP_TEX_COORD_Y (m, 0));
    glVertex2f (x + width, y - height);

    glEnd ();

    disableTexture (s, &rs->textTexture);
    glColor4usv (defaultColor);

    if (!wasBlend)
	glDisable (GL_BLEND);
    glBlendFunc (oldBlendSrc, oldBlendDst);
}

static Bool
ringPaintWindow (CompWindow		 *w,
       		 const WindowPaintAttrib *attrib,
		 const CompTransform	 *transform,
		 Region		         region,
		 unsigned int		 mask)
{
    CompScreen *s = w->screen;
    Bool       status;

    RING_SCREEN (s);

    if (rs->state != RingStateNone)
    {
	WindowPaintAttrib sAttrib = *attrib;
	Bool		  scaled = FALSE;

	RING_WINDOW (w);

    	if (w->mapNum)
	{
	    if (!w->texture->pixmap && !w->bindFailed)
		bindWindow (w);
	}

	if (rw->adjust || rw->slot)
	{
	    scaled = rw->adjust || (rw->slot && rs->paintingSwitcher);
	    mask |= PAINT_WINDOW_NO_CORE_INSTANCE_MASK;
	}
	else if (rs->state != RingStateIn)
	{
	    if (ringGetDarkenBack (s))
	    {
		/* modify brightness of the other windows */
		sAttrib.brightness = sAttrib.brightness / 2;
	    }
	}

	UNWRAP (rs, s, paintWindow);
	status = (*s->paintWindow) (w, &sAttrib, transform, region, mask);
	WRAP (rs, s, paintWindow, ringPaintWindow);

	if (scaled && w->texture->pixmap)
	{
	    FragmentAttrib fragment;
	    CompTransform  wTransform = *transform;

	    if (mask & PAINT_WINDOW_OCCLUSION_DETECTION_MASK)
		return FALSE;

	    initFragmentAttrib (&fragment, &w->lastPaint);

	    if (rw->slot)
	    {
    		fragment.brightness = (float) fragment.brightness * 
		                      rw->slot->depthBrightness;
		if (w->id != rs->selectedWindow)
		    fragment.opacity = (float)fragment.opacity *
			               ringGetInactiveOpacity (s) / 100;
	    }

	    if (w->alpha || fragment.opacity != OPAQUE)
		mask |= PAINT_WINDOW_TRANSLUCENT_MASK;

	    matrixTranslate (&wTransform, w->attrib.x, w->attrib.y, 0.0f);
	    matrixScale (&wTransform, rw->scale, rw->scale, 1.0f);
	    matrixTranslate (&wTransform,
			     rw->tx / rw->scale - w->attrib.x,
			     rw->ty / rw->scale - w->attrib.y,
			     0.0f);

	    glPushMatrix ();
	    glLoadMatrixf (wTransform.m);

	    (*s->drawWindow) (w, &wTransform, &fragment, region,
			      mask | PAINT_WINDOW_TRANSFORMED_MASK);

	    glPopMatrix ();
	}

	if (scaled && (rs->state != RingStateIn) &&
	    ((ringGetOverlayIcon (s) != OverlayIconNone) || 
	     !w->texture->pixmap))
	{
	    CompIcon *icon;

	    icon = getWindowIcon (w, 96, 96);
	    if (!icon)
		icon = w->screen->defaultIcon;

	    if (icon && (icon->texture.name || iconToTexture (w->screen, icon)))
	    {
		REGION iconReg;
		CompMatrix matrix;
		float  scale;
		float  x, y;
		int    width, height;
		int    scaledWinWidth, scaledWinHeight;
		RingOverlayIconEnum iconOverlay = ringGetOverlayIcon (s);

		scaledWinWidth  = w->width  * rw->scale;
		scaledWinHeight = w->height * rw->scale;

		if (!w->texture->pixmap)
		    iconOverlay = OverlayIconBig;

	    	switch (iconOverlay) 
		{
	    	    case OverlayIconNone:
    		    case OverlayIconEmblem:
			scale = (rw->slot) ? rw->slot->depthScale : 1.0f;
			break;
		    case OverlayIconBig:
		    default:
			/* only change opacity if not painting an
			   icon for a minimized window */
			if (w->texture->pixmap)
			    sAttrib.opacity /= 3;
			scale = MIN (((float) scaledWinWidth / icon->width),
				     ((float) scaledWinHeight / icon->height));
			break;
		}

		width  = icon->width  * scale;
		height = icon->height * scale;

	    	switch (iconOverlay) {
		    case OverlayIconNone:
	    	    case OverlayIconEmblem:
    			x = w->attrib.x + scaledWinWidth - width;
			y = w->attrib.y + scaledWinHeight - height;
			break;
		    case OverlayIconBig:
		    default:
			x = w->attrib.x + scaledWinWidth / 2 - width / 2;
			y = w->attrib.y + scaledWinHeight / 2 - height / 2;
			break;
		}

		x += rw->tx;
		y += rw->ty;

		mask |= PAINT_WINDOW_BLEND_MASK;
		
		/* if we paint the icon for a minimized window, we need
		   to force the usage of a good texture filter */
		if (!w->texture->pixmap)
		    mask |= PAINT_WINDOW_TRANSFORMED_MASK;

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

		iconReg.extents.x1 = w->attrib.x;
		iconReg.extents.y1 = w->attrib.y;
		iconReg.extents.x2 = w->attrib.x + icon->width;
		iconReg.extents.y2 = w->attrib.y + icon->height;

		matrix = icon->texture.matrix;
		matrix.x0 -= (w->attrib.x * icon->texture.matrix.xx);
		matrix.y0 -= (w->attrib.y * icon->texture.matrix.yy);

		w->vCount = w->indexCount = 0;
		(*w->screen->addWindowGeometry) (w, &matrix, 1,
	    					 &iconReg, &infiniteRegion);

		if (w->vCount)
		{
		    FragmentAttrib fragment;
		    CompTransform  wTransform = *transform;

		    if (!w->texture->pixmap)
		    {
			/* the fade plugin does weird things to
			   w->paint.opacity, so better use the atom value */
			sAttrib.opacity = w->opacity;
		    }

		    initFragmentAttrib (&fragment, &sAttrib);

		    if (rw->slot)
			fragment.brightness = (float) fragment.brightness * 
			                      rw->slot->depthBrightness;

		    matrixTranslate (&wTransform, 
				     w->attrib.x, w->attrib.y, 0.0f);
		    matrixScale (&wTransform, scale, scale, 1.0f);
		    matrixTranslate (&wTransform,
				     (x - w->attrib.x) / scale - w->attrib.x,
				     (y - w->attrib.y) / scale - w->attrib.y,
				     0.0f);

		    glPushMatrix ();
		    glLoadMatrixf (wTransform.m);

		    (*w->screen->drawWindowTexture) (w,
						     &icon->texture, &fragment,
						     mask);

		    glPopMatrix ();
		}
	    }
	}
    }
    else
    {
	UNWRAP (rs, s, paintWindow);
	status = (*s->paintWindow) (w, attrib, transform, region, mask);
	WRAP (rs, s, paintWindow, ringPaintWindow);
    }

    return status;
}

static inline float 
ringLinearInterpolation(float valX, 
			float minX, float maxX, 
			float minY, float maxY)
{
    double factor = (maxY - minY) / (maxX - minX);
    return (minY + (factor * (valX - minX)));
}

static int
compareWindows (const void *elem1,
		const void *elem2)
{
    CompWindow *w1 = *((CompWindow **) elem1);
    CompWindow *w2 = *((CompWindow **) elem2);

    if (w1->mapNum && !w2->mapNum)
	return -1;

    if (w2->mapNum && !w1->mapNum)
	return 1;

    return w2->activeNum - w1->activeNum;
}

static int 
compareRingWindowDepth (const void *elem1, 
			const void *elem2)
{
    RingSlot *a1   = *(((RingDrawSlot *) elem1)->slot);
    RingSlot *a2   = *(((RingDrawSlot *) elem2)->slot);

    if (a1->y < a2->y)
	return -1;
    else if (a1->y > a2->y)
	return 1;
    else
	return 0;
}

static Bool
layoutThumbs (CompScreen *s)
{
    RING_SCREEN (s);
    CompWindow *w;
    float baseAngle = (2 * PI * rs->rotTarget) / 3600;
    float angle;
    int index;
    int ww, wh;
    float xScale, yScale;

    if ((rs->state == RingStateNone) || (rs->state == RingStateIn))
	return FALSE;

    int ox1, ox2, oy1, oy2;
    getCurrentOutputExtents (s, &ox1, &oy1, &ox2, &oy2);
    /* the center of the ellipse is in the middle 
       of the used output device */
    int centerX = ox1 + (ox2 - ox1) / 2;
    int centerY = oy1 + (oy2 - oy1) / 2;
    int ellipseA = ((ox2 - ox1) * ringGetRingWidth (s)) / 200;
    int ellipseB = ((oy2 - oy1) * ringGetRingHeight (s)) / 200;

    for (index = 0; index < rs->nWindows; index++)
    {
	w = rs->windows[index];
	RING_WINDOW (w);

	if (!rw->slot)
	    rw->slot = malloc (sizeof(RingSlot));

	if (!rw->slot)
	    return FALSE;

	/* we subtract the angle from the base angle 
	   to order the windows clockwise */
	angle = baseAngle - (index * (2 * PI / rs->nWindows));

	rw->slot->x = centerX + (ringGetRingClockwise(s) ? -1 : 1) * 
	                        ((float)ellipseA * sin(angle));
	rw->slot->y = centerY + ((float)ellipseB * cos(angle));

	ww = w->width  + w->input.left + w->input.right;
	wh = w->height + w->input.top  + w->input.bottom;

	if (ww > ringGetThumbWidth (s))
	    xScale = (float)(ringGetThumbWidth (s)) / (float)ww;
	else
	    xScale = 1.0f;

	if (wh > ringGetThumbHeight (s))
	    yScale = (float)(ringGetThumbHeight (s)) / (float)wh;
	else
	    yScale = 1.0f;

	rw->slot->scale = MIN (xScale, yScale);

	/* scale and brightness are obtained by doing a linear inter-
	   polation - the y positions are the x values for the interpolation
	   (the larger Y is, the nearer is the window), and scale/brightness
	   are the y values for the interpolation */
	rw->slot->depthScale = 
	    ringLinearInterpolation (rw->slot->y, 
				     centerY - ellipseB, 
				     centerY + ellipseB, 
				     ringGetMinScale (s),
				     1.0f);

	rw->slot->depthBrightness = 
	    ringLinearInterpolation (rw->slot->y, 
				     centerY - ellipseB, 
				     centerY + ellipseB, 
				     ringGetMinBrightness (s),
				     1.0f);

	rs->drawSlots[index].w    = w;
	rs->drawSlots[index].slot = &rw->slot;
    }

    /* sort the draw list so that the windows with the 
       lowest Y value (the windows being farest away)
       are drawn first */
    qsort (rs->drawSlots, rs->nWindows, sizeof (RingDrawSlot), 
	   compareRingWindowDepth);

    return TRUE;
}

static void
ringAddWindowToList (CompScreen *s,
		     CompWindow *w)
{
    RING_SCREEN (s);

    if (rs->windowsSize <= rs->nWindows)
    {
	rs->windows = realloc (rs->windows,
			       sizeof (CompWindow *) * (rs->nWindows + 32));
	if (!rs->windows)
	    return;

	rs->drawSlots = realloc (rs->drawSlots,
				 sizeof (RingDrawSlot) * (rs->nWindows + 32));

	if (!rs->drawSlots)
	    return;

	rs->windowsSize = rs->nWindows + 32;
    }

    rs->windows[rs->nWindows++] = w;
}

static Bool
ringUpdateWindowList (CompScreen *s)
{
    int i;
    RING_SCREEN (s);

    qsort (rs->windows, rs->nWindows, sizeof (CompWindow *), compareWindows);

    rs->rotTarget = 0;
    for (i = 0; i < rs->nWindows; i++)
    {
	if (rs->windows[i]->id == rs->selectedWindow)
	    break;

	rs->rotTarget += DIST_ROT;
    }

    return layoutThumbs (s);
}

static Bool
ringCreateWindowList (CompScreen *s)
{
    CompWindow *w;

    RING_SCREEN (s);

    rs->nWindows = 0;

    for (w = s->windows; w; w = w->next)
    {
	if (isRingWin (w))
	{
	    RING_WINDOW (w);

	    ringAddWindowToList (s, w);
	    rw->adjust = TRUE;
	}
    }

    return ringUpdateWindowList (s);
}

static void
switchToWindow (CompScreen *s,
		Bool	   toNext)
{
    CompWindow *w;
    int	       cur;

    RING_SCREEN (s);

    if (!rs->grabIndex)
	return;

    for (cur = 0; cur < rs->nWindows; cur++)
    {
	if (rs->windows[cur]->id == rs->selectedWindow)
	    break;
    }

    if (cur == rs->nWindows)
	return;

    if (toNext)
	w = rs->windows[(cur + 1) % rs->nWindows];
    else
	w = rs->windows[(cur + rs->nWindows - 1) % rs->nWindows];

    if (w)
    {
	Window old = rs->selectedWindow;
	rs->selectedWindow = w->id;

	if (old != w->id)
	{
	    if (toNext)
		rs->rotAdjust += DIST_ROT;
	    else
		rs->rotAdjust -= DIST_ROT;

	    rs->rotateAdjust = TRUE;
	    damageScreen (s);
	    ringRenderWindowTitle (s);
	}
    }
}

static int
ringCountWindows (CompScreen *s)
{
    CompWindow *w;
    int	       count = 0;

    for (w = s->windows; w; w = w->next)
    {
	if (isRingWin (w))
	    count++;
    }

    return count;
}

static int adjustRingRotation (CompScreen *s, float chunk)
{
    float dx, adjust, amount;
    int change;

    RING_SCREEN(s);

    dx = rs->rotAdjust;

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

    rs->rVelocity = (amount * rs->rVelocity + adjust) / (amount + 1.0f);

    if (fabs (dx) < 0.1f && fabs (rs->rVelocity) < 0.2f)
    {
	rs->rVelocity = 0.0f;
	rs->rotTarget += rs->rotAdjust;
	rs->rotAdjust = 0;
	return 0;
    }

    change = rs->rVelocity * chunk;
    if (!change)
    {
	if (rs->rVelocity)
	    change = (rs->rotAdjust > 0) ? 1 : -1;
    }

    rs->rotAdjust -= change;
    rs->rotTarget += change;

    if (!layoutThumbs (s))
	return FALSE;

    return TRUE;
}

static int
adjustRingVelocity (CompWindow *w)
{
    float dx, dy, ds, adjust, amount;
    float x1, y1, scale;

    RING_WINDOW (w);

    if (rw->slot)
    {
	scale = rw->slot->scale * rw->slot->depthScale;
	x1 = rw->slot->x - (w->attrib.width * scale) / 2;
	y1 = rw->slot->y - (w->attrib.height * scale) / 2;
    }
    else
    {
	x1 = w->attrib.x;
	y1 = w->attrib.y;
	scale = 1.0f;
    }

    dx = x1 - (w->attrib.x + rw->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;

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

    dy = y1 - (w->attrib.y + rw->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;

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

    ds = scale - rw->scale;
    adjust = ds * 0.1f;
    amount = fabs (ds) * 7.0f;
    if (amount < 0.01f)
	amount = 0.01f;
    else if (amount > 0.15f)
	amount = 0.15f;

    rw->scaleVelocity = (amount * rw->scaleVelocity + adjust) /
	(amount + 1.0f);

    if (fabs (dx) < 0.1f && fabs (rw->xVelocity) < 0.2f &&
	fabs (dy) < 0.1f && fabs (rw->yVelocity) < 0.2f &&
	fabs (ds) < 0.001f && fabs (rw->scaleVelocity) < 0.002f)
    {
	rw->xVelocity = rw->yVelocity = rw->scaleVelocity = 0.0f;
	rw->tx = x1 - w->attrib.x;
	rw->ty = y1 - w->attrib.y;
	rw->scale = scale;

	return 0;
    }

    return 1;
}

static Bool
ringPaintOutput (CompScreen		  *s,
		 const ScreenPaintAttrib *sAttrib,
		 const CompTransform	  *transform,
		 Region		          region,
		 CompOutput		  *output,
		 unsigned int		  mask)
{
    Bool status;

    RING_SCREEN (s);

    if (rs->state != RingStateNone)
	mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_MASK;

    UNWRAP (rs, s, paintOutput);
    status = (*s->paintOutput) (s, sAttrib, transform, region, output, mask);
    WRAP (rs, s, paintOutput, ringPaintOutput);

    if (rs->state != RingStateNone)
    {
	int           i;
	CompWindow    *w;
	CompTransform sTransform = *transform;

	transformToScreenSpace (s, output, -DEFAULT_Z_CAMERA, &sTransform);
	glPushMatrix ();
	glLoadMatrixf (sTransform.m);

	rs->paintingSwitcher = TRUE;

	for (i = 0; i < rs->nWindows; i++)
	{
	    w = rs->drawSlots[i].w;

	    if (rs->drawSlots[i].slot && *(rs->drawSlots[i].slot))
	    {
		(*s->paintWindow) (w, &w->paint, &sTransform,
				   &infiniteRegion, 0);
	    }
	}

	rs->paintingSwitcher = FALSE;

	if (rs->textPixmap && (rs->state != RingStateIn))
	    ringDrawWindowTitle (s);
	
	glPopMatrix ();
    }

    return status;
}

static void
ringPreparePaintScreen (CompScreen *s,
			int	    msSinceLastPaint)
{
    RING_SCREEN (s);

    if (rs->state != RingStateNone && 
	(rs->moreAdjust || rs->rotateAdjust))
    {
	CompWindow *w;
	int        steps;
	float      amount, chunk;

	amount = msSinceLastPaint * 0.05f * ringGetSpeed (s);
	steps  = amount / (0.5f * ringGetTimestep (s));

	if (!steps) 
	    steps = 1;
	chunk  = amount / (float) steps;

	while (steps--)
	{
	    rs->rotateAdjust = adjustRingRotation (s, chunk);
	    rs->moreAdjust = FALSE;

	    for (w = s->windows; w; w = w->next)
	    {
		RING_WINDOW (w);

		if (rw->adjust)
		{
		    rw->adjust = adjustRingVelocity (w);

		    rs->moreAdjust |= rw->adjust;

		    rw->tx += rw->xVelocity * chunk;
		    rw->ty += rw->yVelocity * chunk;
		    rw->scale += rw->scaleVelocity * chunk;
		}
		else if (rw->slot)
		{
		    rw->scale = rw->slot->scale * rw->slot->depthScale;
	    	    rw->tx = rw->slot->x - w->attrib.x -
			     (w->attrib.width * rw->scale) / 2;
	    	    rw->ty = rw->slot->y - w->attrib.y - 
			     (w->attrib.height * rw->scale) / 2;
		}
	    }

	    if (!rs->moreAdjust && !rs->rotateAdjust)
		break;
	}
    }

    UNWRAP (rs, s, preparePaintScreen);
    (*s->preparePaintScreen) (s, msSinceLastPaint);
    WRAP (rs, s, preparePaintScreen, ringPreparePaintScreen);
}

static void
ringDonePaintScreen (CompScreen *s)
{
    RING_SCREEN (s);

    if (rs->state != RingStateNone)
    {
	if (rs->moreAdjust)
	{
	    damageScreen (s);
	}
	else
	{
	    if (rs->rotateAdjust)
	    {
		damageScreen (s);
	    }

	    if (rs->state == RingStateIn)
		rs->state = RingStateNone;
	    else if (rs->state == RingStateOut)
		rs->state = RingStateSwitching;
	}
    }

    UNWRAP (rs, s, donePaintScreen);
    (*s->donePaintScreen) (s);
    WRAP (rs, s, donePaintScreen, ringDonePaintScreen);
}

static Bool
ringTerminate (CompDisplay     *d,
	       CompAction      *action,
	       CompActionState state,
	       CompOption      *option,
	       int	        nOption)
{
    CompScreen *s;
    Window     xid;

    xid = getIntOptionNamed (option, nOption, "root", 0);

    for (s = d->screens; s; s = s->next)
    {
	RING_SCREEN (s);

	if (xid && s->root != xid)
	    continue;

	if (rs->grabIndex)
    	{
    	    removeScreenGrab (s, rs->grabIndex, 0);
    	    rs->grabIndex = 0;
	}

	if (rs->state != RingStateNone)
    	{
    	    CompWindow *w;

    	    for (w = s->windows; w; w = w->next)
    	    {
    		RING_WINDOW (w);
    
		if (rw->slot)
		{
		    free (rw->slot);
		    rw->slot = NULL;

    		    rw->adjust = TRUE;
		}
	    }
	    rs->moreAdjust = TRUE;
    	    rs->state = RingStateIn;
    	    damageScreen (s);

	    if (!(state & CompActionStateCancel) && rs->selectedWindow)
	    {
		w = findWindowAtScreen (s, rs->selectedWindow);
		if (w)
		    sendWindowActivationRequest (s, w->id);
	    }
	}
    }

    return FALSE;
}

static Bool
ringInitiate (CompScreen      *s,
 	      CompAction      *action,
	      CompActionState state,
	      CompOption      *option,
	      int	      nOption)
{
    CompMatch *match;
    int       count; 

    RING_SCREEN (s);

    if (otherScreenGrabExist (s, "ring", 0))
	return FALSE;
	   
    rs->currentMatch = ringGetWindowMatch (s);

    match = getMatchOptionNamed (option, nOption, "match", NULL);
    if (match)
    {
	matchFini (&rs->match);
	matchInit (&rs->match);
	if (matchCopy (&rs->match, match))
	{
	    matchUpdate (s->display, &rs->match);
	    rs->currentMatch = &rs->match;
	}
    }

    count = ringCountWindows (s);

    if (count < 1)
	return FALSE;

    if (!rs->grabIndex)
    {
	if (ringGetSelectWithMouse (s))
	    rs->grabIndex = pushScreenGrab (s, rs->cursor, "ring");
	else
	    rs->grabIndex = pushScreenGrab (s, s->invisibleCursor, "ring");
    }

    if (rs->grabIndex)
    {
	rs->state = RingStateOut;

	if (!ringCreateWindowList (s))
	    return FALSE;

    	rs->selectedWindow = rs->windows[0]->id;
	ringRenderWindowTitle (s);
	rs->rotTarget = 0;

    	rs->moreAdjust = TRUE;
	damageScreen (s);
    }

    if (state & CompActionStateInitButton)
	action->state |= CompActionStateTermButton;

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

    return TRUE;
}

static Bool
ringDoSwitch (CompDisplay     *d,
	      CompAction      *action,
	      CompActionState state,
	      CompOption      *option,
	      int             nOption,
	      Bool            nextWindow,
	      RingType        type)
{
    CompScreen *s;
    Window     xid;
    Bool       ret = TRUE;

    xid = getIntOptionNamed (option, nOption, "root", 0);

    s = findScreenAtDisplay (d, xid);
    if (s)
    {
	RING_SCREEN (s);

	if ((rs->state == RingStateNone) || (rs->state == RingStateIn))
	{
	    if (type == RingTypeGroup)
	    {
    		CompWindow *w;
    		w = findWindowAtDisplay (d, getIntOptionNamed (option, nOption,
    							       "window", 0));
    		if (w)
    		{
    		    rs->type = RingTypeGroup;
    		    rs->clientLeader = 
			(w->clientLeader) ? w->clientLeader : w->id;
		    ret = ringInitiate (s, action, state, option, nOption);
		}
	    }
	    else
	    {
		rs->type = type;
		ret = ringInitiate (s, action, state, option, nOption);
	    }

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

	    if (state & CompActionStateInitButton)
		action->state |= CompActionStateTermButton;

	    if (state & CompActionStateInitEdge)
		action->state |= CompActionStateTermEdge;
	}

	if (ret)
    	    switchToWindow (s, nextWindow);
    }

    return ret;
}

static Bool
ringNext (CompDisplay     *d,
  	  CompAction      *action,
	  CompActionState state,
	  CompOption      *option,
	  int	           nOption)
{
    return ringDoSwitch (d, action, state, option, nOption,
			 TRUE, RingTypeNormal); 
}

static Bool
ringPrev (CompDisplay     *d,
  	  CompAction      *action,
	  CompActionState state,
	  CompOption      *option,
	  int	           nOption)
{
    return ringDoSwitch (d, action, state, option, nOption,
			 FALSE, RingTypeNormal); 
}

static Bool
ringNextAll (CompDisplay     *d,
	     CompAction      *action,
   	     CompActionState state,
   	     CompOption      *option,
   	     int	     nOption)
{
    return ringDoSwitch (d, action, state, option, nOption,
			 TRUE, RingTypeAll); 
}

static Bool
ringPrevAll (CompDisplay     *d,
	     CompAction      *action,
   	     CompActionState state,
   	     CompOption      *option,
   	     int	     nOption)
{
    return ringDoSwitch (d, action, state, option, nOption,
			 FALSE, RingTypeAll); 
}

static Bool
ringNextGroup (CompDisplay     *d,
     	       CompAction      *action,
     	       CompActionState state,
     	       CompOption      *option,
     	       int	       nOption)
{
    return ringDoSwitch (d, action, state, option, nOption,
			 TRUE, RingTypeGroup); 
}

static Bool
ringPrevGroup (CompDisplay     *d,
     	       CompAction      *action,
     	       CompActionState state,
     	       CompOption      *option,
     	       int	       nOption)
{
    return ringDoSwitch (d, action, state, option, nOption,
			 FALSE, RingTypeGroup); 
}

static void
ringWindowSelectAt (CompScreen *s, int x, int y)
{
    CompOption o;
    CompWindow *w;
    int i;

    RING_SCREEN (s);

    if (!ringGetSelectWithMouse (s))
	return;
 
    /* first find the top-most window the mouse 
       pointer is over */
    for (i = rs->nWindows - 1; i >= 0; i--)
    {
    	w = rs->drawSlots[i].w;

    	if (rs->drawSlots[i].slot && *(rs->drawSlots[i].slot))
	{
	    RING_WINDOW (w);

    	    if ((x >= (rw->tx + w->attrib.x)) &&
		(x <= (rw->tx + w->attrib.x + (w->width * rw->scale))) &&
		(y >= (rw->ty + w->attrib.y)) &&
		(y <= (rw->ty + w->attrib.y + (w->height * rw->scale))))
	    {
		/* we have found one, select it */
		rs->selectedWindow = w->id;
		break;
	    }
	}
    }

    if (i >= 0)
    {
    	o.type = CompOptionTypeInt;
    	o.name = "root";
	o.value.i = s->root;

	ringTerminate(s->display, NULL, 0, &o, 1);
    }
}

static void 
ringWindowRemove (CompDisplay * d, 
		  Window id)
{
    CompWindow *w;

    w = findWindowAtDisplay (d, id);
    if (w)
    {
	Bool inList = FALSE;
	int j, i = 0;
	Window selected;

	RING_SCREEN(w->screen);

	if (rs->state == RingStateNone)
	    return;

	if (isRingWin(w))
    	    return;

	selected = rs->selectedWindow;

	while (i < rs->nWindows)
	{
    	    if (w->id == rs->windows[i]->id)
	    {
		inList = TRUE;

		if (w->id == selected)
		{
		    if (i < (rs->nWindows - 1))
			selected = rs->windows[i + 1]->id;
    		    else
			selected = rs->windows[0]->id;

		    rs->selectedWindow = selected;
		}

		rs->nWindows--;
		for (j = i; j < rs->nWindows; j++)
		    rs->windows[j] = rs->windows[j + 1];
	    }
	    else
	    {
		i++;
	    }
	}

	if (!inList)
	    return;

	if (rs->nWindows == 0)
	{
	    CompOption o;

	    o.type = CompOptionTypeInt;
	    o.name = "root";
	    o.value.i = w->screen->root;

	    ringTerminate (d, NULL, 0, &o, 1);
	    return;
	}

	if (!rs->grabIndex)
	    return;

	if (ringUpdateWindowList (w->screen))
	{
	    rs->moreAdjust = TRUE;
	    rs->state = RingStateOut;
	    damageScreen (w->screen);
	}
    }
}

static void
ringHandleEvent (CompDisplay *d,
		 XEvent      *event)
{
    RING_DISPLAY (d);

    UNWRAP (rd, d, handleEvent);
    (*d->handleEvent) (d, event);
    WRAP (rd, d, handleEvent, ringHandleEvent);

    switch (event->type) {
    case PropertyNotify:
	if (event->xproperty.atom == XA_WM_NAME)
	{
	    CompWindow *w;
	    w = findWindowAtDisplay (d, event->xproperty.window);
	    if (w)
	    {
    		RING_SCREEN (w->screen);
    		if (rs->grabIndex && (w->id == rs->selectedWindow))
    		{
    		    ringRenderWindowTitle (w->screen);
    		    damageScreen (w->screen);
		}
	    }
	}
	break;
    case ButtonPress:
	if (event->xbutton.button == Button1)
	{
	    CompScreen *s;
	    s = findScreenAtDisplay (d, event->xbutton.root);
	    if (s)
	    {
    		RING_SCREEN (s);

    		if (rs->grabIndex)
    		    ringWindowSelectAt (s, 
					event->xbutton.x_root, 
					event->xbutton.y_root);
	    }
	}
	break;
    case UnmapNotify:
	ringWindowRemove (d, event->xunmap.window);
	break;
    case DestroyNotify:
	ringWindowRemove (d, event->xdestroywindow.window);
	break;
    }
}

static Bool
ringDamageWindowRect (CompWindow *w,
		      Bool	  initial,
		      BoxPtr     rect)
{
    Bool status = FALSE;

    RING_SCREEN (w->screen);

    if (initial)
    {
	if (rs->grabIndex && isRingWin (w))
	{
	    ringAddWindowToList (w->screen, w);
	    if (ringUpdateWindowList (w->screen))
	    {
		RING_WINDOW (w);

		rw->adjust = TRUE;
		rs->moreAdjust = TRUE;
		rs->state = RingStateOut;
		damageScreen (w->screen);
	    }
	}
    }
    else if (rs->state == RingStateSwitching)
    {
	RING_WINDOW (w);

	if (rw->slot)
	{
	    damageTransformedWindowRect (w,
					 rw->scale, rw->scale,
					 rw->tx, rw->ty,
					 rect);

	    status = TRUE;
	}
    }

    UNWRAP (rs, w->screen, damageWindowRect);
    status |= (*w->screen->damageWindowRect) (w, initial, rect);
    WRAP (rs, w->screen, damageWindowRect, ringDamageWindowRect);

    return status;
}

static Bool
ringInitDisplay (CompPlugin  *p,
		 CompDisplay *d)
{
    RingDisplay *rd;

    rd = malloc (sizeof (RingDisplay));
    if (!rd)
	return FALSE;

    rd->screenPrivateIndex = allocateScreenPrivateIndex (d);
    if (rd->screenPrivateIndex < 0)
    {
	free (rd);
	return FALSE;
    }

    ringSetNextInitiate (d, ringNext);
    ringSetNextTerminate (d, ringTerminate);
    ringSetPrevInitiate (d, ringPrev);
    ringSetPrevTerminate (d, ringTerminate);
    ringSetNextAllInitiate (d, ringNextAll);
    ringSetNextAllTerminate (d, ringTerminate);
    ringSetPrevAllInitiate (d, ringPrevAll);
    ringSetPrevAllTerminate (d, ringTerminate);
    ringSetNextGroupInitiate (d, ringNextGroup);
    ringSetNextGroupTerminate (d, ringTerminate);
    ringSetPrevGroupInitiate (d, ringPrevGroup);
    ringSetPrevGroupTerminate (d, ringTerminate);

    WRAP (rd, d, handleEvent, ringHandleEvent);

    d->privates[displayPrivateIndex].ptr = rd;

    return TRUE;
}

static void
ringFiniDisplay (CompPlugin  *p,
		 CompDisplay *d)
{
    RING_DISPLAY (d);

    freeScreenPrivateIndex (d, rd->screenPrivateIndex);

    UNWRAP (rd, d, handleEvent);

    free (rd);
}

static Bool
ringInitScreen (CompPlugin *p,
		CompScreen *s)
{
    RingScreen *rs;

    RING_DISPLAY (s->display);

    rs = malloc (sizeof (RingScreen));
    if (!rs)
	return FALSE;

    rs->windowPrivateIndex = allocateWindowPrivateIndex (s);
    if (rs->windowPrivateIndex < 0)
    {
	free (rs);
	return FALSE;
    }

    rs->grabIndex = 0;

    rs->state = RingStateNone;

    rs->windows = NULL;
    rs->drawSlots = NULL;
    rs->windowsSize = 0;

    rs->paintingSwitcher = FALSE;

    rs->selectedWindow = None;

    rs->moreAdjust   = FALSE;
    rs->rotateAdjust = FALSE;

    rs->rotAdjust = 0;
    rs->rVelocity = 0;

    rs->textPixmap = None;

    matchInit (&rs->match);

    WRAP (rs, s, preparePaintScreen, ringPreparePaintScreen);
    WRAP (rs, s, donePaintScreen, ringDonePaintScreen);
    WRAP (rs, s, paintOutput, ringPaintOutput);
    WRAP (rs, s, paintWindow, ringPaintWindow);
    WRAP (rs, s, damageWindowRect, ringDamageWindowRect);

    rs->cursor = XCreateFontCursor (s->display->display, XC_left_ptr);

    s->privates[rd->screenPrivateIndex].ptr = rs;

    return TRUE;
}

static void
ringFiniScreen (CompPlugin *p,
		CompScreen *s)
{
    RING_SCREEN (s);

    freeWindowPrivateIndex (s, rs->windowPrivateIndex);

    UNWRAP (rs, s, preparePaintScreen);
    UNWRAP (rs, s, donePaintScreen);
    UNWRAP (rs, s, paintOutput);
    UNWRAP (rs, s, paintWindow);
    UNWRAP (rs, s, damageWindowRect);

    matchFini (&rs->match);

    ringFreeWindowTitle (s);

    XFreeCursor (s->display->display, rs->cursor);

    if (rs->windows)
	free (rs->windows);

    if (rs->drawSlots)
	free (rs->drawSlots);

    free (rs);
}

static Bool
ringInitWindow (CompPlugin *p,
		CompWindow *w)
{
    RingWindow *rw;

    RING_SCREEN (w->screen);

    rw = malloc (sizeof (RingWindow));
    if (!rw)
	return FALSE;

    rw->slot = 0;
    rw->scale = 1.0f;
    rw->tx = rw->ty = 0.0f;
    rw->adjust = FALSE;
    rw->xVelocity = rw->yVelocity = 0.0f;
    rw->scaleVelocity = 0.0f;

    w->privates[rs->windowPrivateIndex].ptr = rw;

    return TRUE;
}

static void
ringFiniWindow (CompPlugin *p,
		CompWindow *w)
{
    RING_WINDOW (w);

    free (rw);
}

static Bool
ringInit (CompPlugin *p)
{
    displayPrivateIndex = allocateDisplayPrivateIndex ();
    if (displayPrivateIndex < 0)
	return FALSE;

    return TRUE;
}

static void
ringFini (CompPlugin *p)
{
    if (displayPrivateIndex >= 0)
	freeDisplayPrivateIndex (displayPrivateIndex);
}

static int
ringGetVersion (CompPlugin *plugin,
		int	    version)
{
    return ABIVERSION;
}

CompPluginVTable ringVTable = {
    "ring",
    ringGetVersion,
    0,
    ringInit,
    ringFini,
    ringInitDisplay,
    ringFiniDisplay,
    ringInitScreen,
    ringFiniScreen,
    ringInitWindow,
    ringFiniWindow,
    0,
    0,
    0,
    0
};

CompPluginVTable *
getCompPluginInfo (void)
{
    return &ringVTable;
}


syntax highlighted by Code2HTML, v. 0.9.1