/*
 * Copyright © 2006 Novell, Inc.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Authors: David Reveman <davidr@novell.com>
 *          James Eady <jmeady@gmail.com> (Lines, arrows, text)
 */

#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <glib.h>
#include <cairo-xlib-xrender.h>
#include <librsvg/rsvg.h>
#include <librsvg/rsvg-cairo.h>

#include <beryl.h>

#define ANNO_INITIATE_BUTTON_DEFAULT            Button1
#define ANNO_INITIATE_BUTTON_MODIFIERS_DEFAULT (CompSuperMask | CompAltMask)

#define ANNO_ERASE_BUTTON_DEFAULT               Button3
#define ANNO_ERASE_BUTTON_MODIFIERS_DEFAULT (CompSuperMask | CompAltMask)

#define ANNO_CLEAR_KEY_DEFAULT                  "k"
#define ANNO_CLEAR_KEY_MODIFIERS_DEFAULT (CompSuperMask | CompAltMask)

// <Drawing Utensils>
#define ANNO_SWITCH_PAINTBRUSH_KEY_DEFAULT      "p"
#define ANNO_SWITCH_PAINTBRUSH_KEY_MODIFIERS_DEFAULT (CompSuperMask | CompAltMask)
#define ANNO_SWITCH_LINE_KEY_DEFAULT            "l"
#define ANNO_SWITCH_LINE_KEY_MODIFIERS_DEFAULT (CompSuperMask | CompAltMask)
#define ANNO_SWITCH_ARROW_KEY_DEFAULT           "a"
#define ANNO_SWITCH_ARROW_KEY_MODIFIERS_DEFAULT (CompSuperMask | CompAltMask)
#define ANNO_PUT_TEXT_KEY_DEFAULT               "t"
#define ANNO_PUT_TEXT_KEY_MODIFIERS_DEFAULT (CompSuperMask | CompAltMask)
// </Drawing Utensils>

#define ANNO_TOOL_PAINTBRUSH        0
#define ANNO_TOOL_LINE              1
#define ANNO_TOOL_ARROW             2

#define ANNO_FILL_COLOR_RED_DEFAULT   0xffff
#define ANNO_FILL_COLOR_GREEN_DEFAULT 0x0000
#define ANNO_FILL_COLOR_BLUE_DEFAULT  0x0000

#define ANNO_STROKE_COLOR_RED_DEFAULT   0x0000
#define ANNO_STROKE_COLOR_GREEN_DEFAULT 0xffff
#define ANNO_STROKE_COLOR_BLUE_DEFAULT  0x0000

#define ANNO_LINE_WIDTH_MIN        0.1f
#define ANNO_LINE_WIDTH_MAX        100.0f
#define ANNO_LINE_WIDTH_DEFAULT    3.0f
#define ANNO_LINE_WIDTH_PRECISION  0.1f

#define ANNO_STROKE_WIDTH_MIN        0.1f
#define ANNO_STROKE_WIDTH_MAX        20.0f
#define ANNO_STROKE_WIDTH_DEFAULT    1.0f
#define ANNO_STROKE_WIDTH_PRECISION  0.1f

static int displayPrivateIndex;

static int annoLastPointerX = 0;
static int annoLastPointerY = 0;

#define ANNO_DISPLAY_OPTION_INITIATE     0
#define ANNO_DISPLAY_OPTION_DRAW         1
#define ANNO_DISPLAY_OPTION_TRANSFORM    2
#define ANNO_DISPLAY_OPTION_SWITCH_CONTEXT   3
#define ANNO_DISPLAY_OPTION_ERASE        4
#define ANNO_DISPLAY_OPTION_CLEAR        5
#define ANNO_DISPLAY_OPTION_FILL_COLOR   6
#define ANNO_DISPLAY_OPTION_STROKE_COLOR 7
#define ANNO_DISPLAY_OPTION_LINE_WIDTH   8
#define ANNO_DISPLAY_OPTION_STROKE_WIDTH 9
#define ANNO_DISPLAY_OPTION_SVG_FILE     10
#define ANNO_DISPLAY_OPTION_TOOL         11
#define ANNO_DISPLAY_OPTION_PRELOAD      12
#define ANNO_DISPLAY_OPTION_USE_PAINTBRUSH 13
#define ANNO_DISPLAY_OPTION_USE_LINE     14
#define ANNO_DISPLAY_OPTION_USE_ARROW    15
#define ANNO_DISPLAY_OPTION_TEXT_STRINGS 16
#define ANNO_DISPLAY_OPTION_PUT_TEXT     17
#define ANNO_DISPLAY_OPTION_NUM          18

#define ANNO_NUMBER_CONTEXTS   12

typedef struct _AnnoDisplay
{
	int screenPrivateIndex;
	HandleEventProc handleEvent;

	CompOption opt[ANNO_DISPLAY_OPTION_NUM];
} AnnoDisplay;

typedef struct _AnnoScreen
{
	PaintScreenProc paintScreen;
	int grabIndex;

	Pixmap pixmap[ANNO_NUMBER_CONTEXTS];
	CompTexture texture[ANNO_NUMBER_CONTEXTS];
	cairo_surface_t *surface[ANNO_NUMBER_CONTEXTS];
	cairo_t *cairo[ANNO_NUMBER_CONTEXTS];
	Bool content[ANNO_NUMBER_CONTEXTS];
	int currentContext;

	RsvgHandle **svgHandle;
	int nSvgHandle;

	Bool eraseMode;
	Bool isDrawing;

	int tool;
	int firstX;
	int firstY;
	int stringIndex;
} AnnoScreen;

#define GET_ANNO_DISPLAY(d)                  \
    ((AnnoDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define ANNO_DISPLAY(d)            \
    AnnoDisplay *ad = GET_ANNO_DISPLAY (d)

#define GET_ANNO_SCREEN(s, ad)                   \
    ((AnnoScreen *) (s)->privates[(ad)->screenPrivateIndex].ptr)

#define ANNO_SCREEN(s)                          \
    AnnoScreen *as = GET_ANNO_SCREEN (s, GET_ANNO_DISPLAY (s->display))

#define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption))

#define NUM_TOOLS (sizeof (tools) / sizeof (tools[0]))

static void annoCairoClear(CompScreen * s, cairo_t * cr, int context)
{
	ANNO_SCREEN(s);

	cairo_save(cr);
	cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
	cairo_paint(cr);
	cairo_restore(cr);

	as->content[context] = FALSE;
}

static cairo_t *annoCairoContext(CompScreen * s, int contextID)
{
	ANNO_SCREEN(s);

	if (!as->cairo || !as->cairo[contextID])
	{
		XRenderPictFormat *format;
		Screen *screen;
		int w, h;

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

		w = s->width;
		h = s->height;

		format = XRenderFindStandardFormat(s->display->display,
										   PictStandardARGB32);

		as->pixmap[contextID] =
				XCreatePixmap(s->display->display, s->root, w, h, 32);

		if (!bindPixmapToTexture(s, &as->texture[contextID],
								 as->pixmap[contextID], w, h, 32))
		{
			fprintf(stderr,
					"%s: Couldn't bind annotate pixmap 0x%x to "
					"texture\n", getProgramName(),
					(int)as->pixmap[contextID]);

			XFreePixmap(s->display->display, as->pixmap[contextID]);

			return NULL;
		}

		as->surface[contextID] =
				cairo_xlib_surface_create_with_xrender_format(s->display->
															  display,
															  as->
															  pixmap
															  [contextID],
															  screen, format,
															  w, h);

		as->cairo[contextID] = cairo_create(as->surface[contextID]);

		annoCairoClear(s, as->cairo[contextID], contextID);
	}

	return as->cairo[contextID];
}

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

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

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

		as->currentContext = contextNumber;

		damageScreen(s);

		return TRUE;
	}
	return FALSE;
}

static void annoSetSourceColor(cairo_t * cr, unsigned short *color)
{
	cairo_set_source_rgba(cr,
						  (double)color[0] / 0xffff,
						  (double)color[1] / 0xffff,
						  (double)color[2] / 0xffff,
						  (double)color[3] / 0xffff);
}

static void
annoDrawCircle(CompScreen * s,
			   int contextID,
			   double xc,
			   double yc,
			   double radius,
			   unsigned short *fillColor,
			   unsigned short *strokeColor, double strokeWidth)
{
	REGION reg;
	cairo_t *cr;

	ANNO_SCREEN(s);

	cr = annoCairoContext(s, contextID);
	if (cr)
	{
		double ex1, ey1, ex2, ey2;

		annoSetSourceColor(cr, fillColor);
		cairo_arc(cr, xc, yc, radius, 0, 2 * M_PI);
		cairo_fill_preserve(cr);
		cairo_set_line_width(cr, strokeWidth);
		cairo_stroke_extents(cr, &ex1, &ey1, &ex2, &ey2);
		annoSetSourceColor(cr, strokeColor);
		cairo_stroke(cr);

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

		reg.extents.x1 = ex1;
		reg.extents.y1 = ey1;
		reg.extents.x2 = ex2;
		reg.extents.y2 = ey2;

		as->content[contextID] = TRUE;
		damageScreenRegion(s, &reg);
	}
}

static void
annoDrawRectangle(CompScreen * s,
				  int contextID,
				  double x,
				  double y,
				  double w,
				  double h,
				  unsigned short *fillColor,
				  unsigned short *strokeColor,
				  double strokeWidth, double angle)
{
	REGION reg;
	cairo_t *cr;

	ANNO_SCREEN(s);

	cr = annoCairoContext(s, contextID);
	if (cr)
	{
		double ex1, ey1, ex2, ey2;

		if (angle != 0)
		{
			cairo_save(cr);
			cairo_translate(cr, x + (w / 2), y + (h / 2));
			cairo_rotate(cr, angle * M_PI / 180);
			cairo_translate(cr, -x - (w / 2), -y - (h / 2));
		}

		annoSetSourceColor(cr, fillColor);
		cairo_rectangle(cr, x, y, w, h);
		cairo_set_line_width(cr, strokeWidth);
		cairo_fill_preserve(cr);
		cairo_stroke_extents(cr, &ex1, &ey1, &ex2, &ey2);
		annoSetSourceColor(cr, strokeColor);
		cairo_stroke(cr);

		if (angle != 0)
			cairo_restore(cr);

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

		reg.extents.x1 = ex1;
		reg.extents.y1 = ey1;
		reg.extents.x2 = ex2;
		reg.extents.y2 = ey2;

		as->content[contextID] = TRUE;

		if (angle == 0)
			damageScreenRegion(s, &reg);
		else
			damageScreen(s);
	}
}

typedef struct _AnnoSvgSizeCallbackData
{
	char *filename;
	CompDisplay *display;
	int handleID;
} AnnoSvgSizeCallbackData;

static void
annoRsvgSizeCallback(gint * width, gint * height, gpointer user_data)
{
/*
fprintf(stderr, "load callback\n");

    char                    *filename;
    CompDisplay             *display;
    int                     nOption = 4;
    int                     handleID;
    CompOption              option[4];
    AnnoSvgSizeCallbackData *data;

    data = (AnnoSvgSizeCallbackData *) user_data;
    display = (CompDisplay *) data->display;
    filename = (char *) data->filename;
    handleID = (int) data->handleID;

    option[0].name = "width";
    option[0].type = CompOptionTypeInt;
    option[0].value.i = *width;

    option[1].name = "height";
    option[1].type = CompOptionTypeInt;
    option[1].value.i = *height;

    option[2].name = "filename";
    option[2].type = CompOptionTypeString;
    option[2].value.s = filename;

    option[3].name = "handle_id";
    option[3].type = CompOptionTypeInt;
    option[3].value.i = handleID;
fprintf(stderr, "load callback handle = %d\n", handleID);
    (*display->handleBerylEvent) (display,
               "annotate",
               "svgLoaded",
               option,
               nOption);*/
}

static void
annoDrawSvg(CompScreen * s,
			int contextID,
			int handleID,
			double x, double y, double angle, double scaleX, double scaleY)
{
	RsvgHandle *svgh;
	cairo_t *cr;

	ANNO_SCREEN(s);

	cr = annoCairoContext(s, contextID);
	if (cr && handleID)
	{
		svgh = as->svgHandle[handleID];

		cairo_save(cr);
		cairo_translate(cr, x, y);
		cairo_scale(cr, scaleX, scaleY);
		rsvg_handle_render_cairo(svgh, cr);
		cairo_restore(cr);

		as->content[contextID] = TRUE;
	}

	damageScreen(s);
}

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

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

	s = findScreenAtDisplay(d, xid);
	if (s)
	{
		GError *svgError = NULL;
		RsvgHandle *svgh;
		AnnoSvgSizeCallbackData callbackData;

		ANNO_SCREEN(s);

		filename = getStringOptionNamed(option, nOption, "filename", "");

		as->nSvgHandle++;

		callbackData.filename = filename;
		callbackData.display = s->display;
		callbackData.handleID = as->nSvgHandle;

		svgh = rsvg_handle_new_from_file(filename, &svgError);
		rsvg_handle_set_size_callback(svgh, annoRsvgSizeCallback,
									  &callbackData, NULL);

		if (svgError)
		{
			fprintf(stderr, "%s: Error loading svg %s\n", getProgramName(),
					filename);
			as->nSvgHandle--;
			rsvg_handle_free(svgh);

			return FALSE;
		}

		as->svgHandle =
				realloc(as->svgHandle, sizeof(RsvgHandle) * as->nSvgHandle);
		as->svgHandle[as->nSvgHandle] = svgh;


		option[0].name = "filename";
		option[0].type = CompOptionTypeString;
		option[0].value.s = filename;

		option[1].name = "handle_id";
		option[1].type = CompOptionTypeInt;
		option[1].value.i = as->nSvgHandle;

		(*d->handleBerylEvent) (d, "annotate", "svgLoaded", option, nOption);


		return TRUE;
	}
	return FALSE;
}

static void
annoDrawLine(CompScreen * s,
			 int contextID,
			 double x1, double y1,
			 double x2, double y2, double width, unsigned short *color)
{
	REGION reg;
	cairo_t *cr;

	ANNO_SCREEN(s);

	cr = annoCairoContext(s, contextID);
	if (cr)
	{
		double ex1, ey1, ex2, ey2;

		cairo_set_line_width(cr, width);
		cairo_move_to(cr, x1, y1);
		cairo_line_to(cr, x2, y2);
		cairo_stroke_extents(cr, &ex1, &ey1, &ex2, &ey2);
		annoSetSourceColor(cr, color);
		if (as->eraseMode)
		{
			cairo_set_operator(cr,CAIRO_OPERATOR_SOURCE);
			cairo_pattern_t * cp = cairo_pattern_create_rgba(1.0f,1.0f,1.0f,0.0f);
			cairo_set_source(cr,cp);
		}
		cairo_stroke(cr);

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

		reg.extents.x1 = ex1;
		reg.extents.y1 = ey1;
		reg.extents.x2 = ex2;
		reg.extents.y2 = ey2;

		as->content[contextID] = TRUE;
		//damageScreenRegion (s, &reg);
		damageScreen(s);
	}
}

static void
annoDrawArrow(CompScreen * s,
			  int contextID,
			  double x1, double y1,
			  double x2, double y2, double width, unsigned short *color)
{
	double startX = x1, endX = x2, startY = y1, endY = y2;
	float shortLength = width * width;
	float dx = startX - endX;
	float dy = startY - endY;
	float length = sqrt(dx * dx + dy * dy);

	if (dx < 0)
		dx *= -1;
	if (dy < 0)
		dy *= -1;

	float shortX = (dx / length) * shortLength;
	float shortY = (dy / length) * shortLength;


	int triangleX[3];
	int triangleY[3];

	if (endX > startX && endY < startY)
	{
		endX -= shortX;
		endY += shortY;
		triangleX[0] = endX + shortY;
		triangleY[0] = endY + shortX;
		triangleX[1] = endX - shortY;
		triangleY[1] = endY - shortX;
	}
	else if (endX < startX && endY > startY)
	{
		endX += shortX;
		endY -= shortY;
		triangleX[0] = endX + shortY;
		triangleY[0] = endY + shortX;
		triangleX[1] = endX - shortY;
		triangleY[1] = endY - shortX;
	}
	else if (endX < startX && endY < startY)
	{
		endX += shortX;
		endY += shortY;
		triangleX[0] = endX + shortY;
		triangleY[0] = endY - shortX;
		triangleX[1] = endX - shortY;
		triangleY[1] = endY + shortX;
	}
	else						// if( endX > startX && endY > startY )
	{
		endX -= shortX;
		endY -= shortY;
		triangleX[0] = endX - shortY;
		triangleY[0] = endY + shortX;
		triangleX[1] = endX + shortY;
		triangleY[1] = endY - shortX;
	}

	triangleX[2] = x2;
	triangleY[2] = y2;

	// Draw the arrow handle...
	annoDrawLine(s, contextID, startX, startY, endX, endY, width, color);

	// Draw the arrow
	annoDrawLine(s, contextID,
				 triangleX[0], triangleY[0],
				 triangleX[1], triangleY[1], width, color);

	annoDrawLine(s, contextID,
				 triangleX[0], triangleY[0],
				 triangleX[2], triangleY[2], width, color);

	annoDrawLine(s, contextID,
				 triangleX[2], triangleY[2],
				 triangleX[1], triangleY[1], width, color);
}

static void
annoDrawText(CompScreen * s,
			 int contextID,
			 double x,
			 double y,
			 char *text,
			 char *fontFamily,
			 double fontSize,
			 int fontSlant,
			 int fontWeight,
			 unsigned short *fillColor,
			 unsigned short *strokeColor, double strokeWidth)
{
	REGION reg;
	cairo_t *cr;

	ANNO_SCREEN(s);

	cr = annoCairoContext(s, contextID);
	if (cr)
	{
		cairo_text_extents_t extents;

		cairo_set_line_width(cr, strokeWidth);
		annoSetSourceColor(cr, fillColor);
		cairo_select_font_face(cr, fontFamily, fontSlant, fontWeight);
		cairo_set_font_size(cr, fontSize);
		cairo_text_extents(cr, text, &extents);
		cairo_save(cr);
		cairo_move_to(cr, x, y);
		cairo_text_path(cr, text);
		cairo_fill_preserve(cr);
		annoSetSourceColor(cr, strokeColor);
		cairo_stroke(cr);
		cairo_restore(cr);

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

		reg.extents.x1 = x;
		reg.extents.y1 = y + extents.y_bearing - 2.0;
		reg.extents.x2 = x + extents.width + 20.0;
		reg.extents.y2 = y + extents.height;

		as->content[contextID] = TRUE;
		damageScreenRegion(s, &reg);
	}
}

static Bool
annoCanvasTransform(CompDisplay * d,
					CompAction * action,
					CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	Window xid;
	double p1, p2;
	char *type;

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

	s = findScreenAtDisplay(d, xid);
	if (s)
	{
		cairo_t *cr;
		int contextID;

		ANNO_SCREEN(s);

		contextID =
				getIntOptionNamed(option, nOption, "context",
								  as->currentContext);
		cr = annoCairoContext(s, contextID);
		if (cr)
		{
			type = getStringOptionNamed(option, nOption, "type", "");
			if (type)
			{
				if (strcasecmp(type, "save") == 0)
				{
					cairo_save(cr);
					return TRUE;
				}
				else if (strcasecmp(type, "restore") == 0)
				{
					cairo_restore(cr);
					return TRUE;
				}
				else if (strcasecmp(type, "translate") == 0)
				{
					p1 = getFloatOptionNamed(option, nOption, "x", 0.0);
					p2 = getFloatOptionNamed(option, nOption, "y", 0.0);
					if (p1 || p2)
					{
						cairo_translate(cr, p1, p2);
						return TRUE;
					}
					return FALSE;
				}
				else if (strcasecmp(type, "scale") == 0)
				{
					p1 = getFloatOptionNamed(option, nOption, "x", 0.0);
					p2 = getFloatOptionNamed(option, nOption, "y", 0.0);
					if (p1 || p2)
					{
						cairo_scale(cr, p1, p2);
						return TRUE;
					}
					return FALSE;
				}
				else if (strcasecmp(type, "rotate") == 0)
				{
					p1 = getFloatOptionNamed(option, nOption, "angle", 0.0);
					if (p1)
					{
						cairo_rotate(cr, p1);
						return TRUE;
					}
					return FALSE;
				}
			}
		}
	}
	return FALSE;
}

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

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

	s = findScreenAtDisplay(d, xid);
	if (s)
	{
		cairo_t *cr;
		int context;

		ANNO_SCREEN(s);

		context =
				getIntOptionNamed(option, nOption, "context",
								  as->currentContext);
		cr = annoCairoContext(s, context);
		if (cr)
		{
			char *tool;
			unsigned short *fillColor, *strokeColor;
			double lineWidth, strokeWidth, angle;
			int context;

			ANNO_DISPLAY(d);

			context =
					getIntOptionNamed(option, nOption, "context",
									  as->currentContext);

			tool = getStringOptionNamed(option, nOption, "tool", "line");

			cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
			cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);

			fillColor = ad->opt[ANNO_DISPLAY_OPTION_FILL_COLOR].value.c;
			fillColor = getColorOptionNamed(option, nOption, "fill_color",
											fillColor);

			strokeColor = ad->opt[ANNO_DISPLAY_OPTION_STROKE_COLOR].value.c;
			strokeColor = getColorOptionNamed(option, nOption,
											  "stroke_color", strokeColor);

			strokeWidth = ad->opt[ANNO_DISPLAY_OPTION_STROKE_WIDTH].value.f;
			strokeWidth =
					getFloatOptionNamed(option, nOption, "stroke_width",
										strokeWidth);

			lineWidth = ad->opt[ANNO_DISPLAY_OPTION_LINE_WIDTH].value.f;
			lineWidth = getFloatOptionNamed(option, nOption, "line_width",
											lineWidth);

			angle = getFloatOptionNamed(option, nOption, "angle", 0.0);

			if (strcasecmp(tool, "rectangle") == 0)
			{
				double x, y, w, h;

				x = getFloatOptionNamed(option, nOption, "x", 0);
				y = getFloatOptionNamed(option, nOption, "y", 0);
				w = getFloatOptionNamed(option, nOption, "w", 100);
				h = getFloatOptionNamed(option, nOption, "h", 100);

				annoDrawRectangle(s, context, x, y, w, h, fillColor,
								  strokeColor, strokeWidth, angle);
			}
			else if (strcasecmp(tool, "circle") == 0)
			{
				double xc, yc, r;

				xc = getFloatOptionNamed(option, nOption, "xc", 0);
				yc = getFloatOptionNamed(option, nOption, "yc", 0);
				r = getFloatOptionNamed(option, nOption, "radius", 100);

				annoDrawCircle(s, context, xc, yc, r, fillColor,
							   strokeColor, strokeWidth);
			}
			else if (strcasecmp(tool, "line") == 0)
			{
				double x1, y1, x2, y2;

				x1 = getFloatOptionNamed(option, nOption, "x1", 0);
				y1 = getFloatOptionNamed(option, nOption, "y1", 0);
				x2 = getFloatOptionNamed(option, nOption, "x2", 100);
				y2 = getFloatOptionNamed(option, nOption, "y2", 100);

				annoDrawLine(s, context, x1, y1, x2, y2, lineWidth,
							 fillColor);
			}
			else if (strcasecmp(tool, "text") == 0)
			{
				double x, y, size;
				char *text, *family;
				unsigned int slant, weight;
				char *str;

				str = getStringOptionNamed(option, nOption, "slant", "");
				if (strcasecmp(str, "oblique") == 0)
					slant = CAIRO_FONT_SLANT_OBLIQUE;
				else if (strcasecmp(str, "italic") == 0)
					slant = CAIRO_FONT_SLANT_ITALIC;
				else
					slant = CAIRO_FONT_SLANT_NORMAL;

				str = getStringOptionNamed(option, nOption, "weight", "");
				if (strcasecmp(str, "bold") == 0)
					weight = CAIRO_FONT_WEIGHT_BOLD;
				else
					weight = CAIRO_FONT_WEIGHT_NORMAL;

				x = getFloatOptionNamed(option, nOption, "x", 0);
				y = getFloatOptionNamed(option, nOption, "y", 0);
				text = getStringOptionNamed(option, nOption, "text", "");
				family = getStringOptionNamed(option, nOption, "family",
											  "Sans");
				size = getFloatOptionNamed(option, nOption, "size", 36.0);

				annoDrawText(s, context, x, y, text, family, size,
							 slant, weight, fillColor,
							 strokeColor, strokeWidth);
			}
			else if (strcasecmp(tool, "svg") == 0)
			{
				double x, y, scaleX, scaleY;
				int handleID;

				x = getFloatOptionNamed(option, nOption, "x", 0);
				y = getFloatOptionNamed(option, nOption, "y", 0);
				scaleX = getFloatOptionNamed(option, nOption, "scale_x", 1.0);
				scaleY = getFloatOptionNamed(option, nOption, "scale_y", 1.0);
				handleID = getIntOptionNamed(option, nOption, "handle_id", 0);

				annoDrawSvg(s, context, handleID, x, y, angle, scaleX,
							scaleY);
			}

		}
	}

	return FALSE;
}

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

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

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

		if (otherScreenGrabExist(s, 0))
			return FALSE;

		if (!as->grabIndex)
			as->grabIndex = pushScreenGrab(s, None, "annotate");

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

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

		annoLastPointerX = d->pointerX;
		annoLastPointerY = d->pointerY;

		if (!as->isDrawing)
		{
			as->firstX = d->pointerX;
			as->firstY = d->pointerY;
			as->isDrawing = TRUE;
		}

		as->eraseMode = FALSE;
	}

	return TRUE;
}

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

	ANNO_DISPLAY(d);

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

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


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

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

		if (as->tool == ANNO_TOOL_LINE)
		{
			annoDrawLine(s, as->currentContext,
						 as->firstX, as->firstY,
						 d->pointerX, d->pointerY,
						 ad->opt[ANNO_DISPLAY_OPTION_LINE_WIDTH].value.f,
						 ad->opt[ANNO_DISPLAY_OPTION_FILL_COLOR].value.c);
		}
		else if (as->tool == ANNO_TOOL_ARROW)
		{
			annoDrawArrow(s, as->currentContext, as->firstX, as->firstY,
						  d->pointerX, d->pointerY,
						  ad->opt[ANNO_DISPLAY_OPTION_LINE_WIDTH].value.f,
						  ad->opt[ANNO_DISPLAY_OPTION_FILL_COLOR].value.c);

		}
		damageScreen(s);
		as->isDrawing = FALSE;
	}

	action->state &= ~(CompActionStateTermKey | CompActionStateTermButton);

	return FALSE;
}

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

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

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

		if (otherScreenGrabExist(s, 0))
			return FALSE;

		if (!as->grabIndex)
			as->grabIndex = pushScreenGrab(s, None, "annotate");

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

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

		annoLastPointerX = d->pointerX;
		annoLastPointerY = d->pointerY;

		as->eraseMode = TRUE;
	}

	return TRUE;
}

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

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

	s = findScreenAtDisplay(d, xid);
	if (s)
	{
		int contextID;

		ANNO_SCREEN(s);

		contextID = getIntOptionNamed(option, nOption, "context",
									  as->currentContext);

		if (as->content[contextID])
		{
			cairo_t *cr;

			cr = annoCairoContext(s, contextID);
			if (cr)
				annoCairoClear(s, as->cairo[contextID], contextID);

			as->content[contextID] = FALSE;

			if (contextID == as->currentContext)
				damageScreen(s);
		}

		return TRUE;
	}

	return FALSE;
}

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

	xid = getIntOptionNamed(option, nOption, "root", 0);
	s = findScreenAtDisplay(d, xid);
	if (!s)
		return FALSE;

	ANNO_SCREEN(s);

	as->tool = ANNO_TOOL_PAINTBRUSH;
	return TRUE;
}

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

	xid = getIntOptionNamed(option, nOption, "root", 0);
	s = findScreenAtDisplay(d, xid);
	if (!s)
		return FALSE;
	ANNO_SCREEN(s);

	as->tool = ANNO_TOOL_LINE;
	return TRUE;
}
static Bool
annoUseArrow(CompDisplay * d,
			 CompAction * action,
			 CompActionState state, CompOption * option, int nOption)
{
	CompScreen *s;
	Window xid;

	xid = getIntOptionNamed(option, nOption, "root", 0);
	s = findScreenAtDisplay(d, xid);
	if (!s)
		return FALSE;
	ANNO_SCREEN(s);

	as->tool = ANNO_TOOL_ARROW;
	return TRUE;
}

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

	xid = getIntOptionNamed(option, nOption, "root", 0);
	s = findScreenAtDisplay(d, xid);
	if (!s)
		return FALSE;
	ANNO_SCREEN(s);
	ANNO_DISPLAY(d);

	int x, y;

	x = d->pointerX;
	y = d->pointerY;

	if (ad->opt[ANNO_DISPLAY_OPTION_TEXT_STRINGS].value.list.nValue == 0)
	{
		annoDrawText(s, as->currentContext, x, y,
					 "Undefined - Configure Annotate Text", "Sans", 24, 0, 0,
					 ad->opt[ANNO_DISPLAY_OPTION_FILL_COLOR].value.c,
					 ad->opt[ANNO_DISPLAY_OPTION_FILL_COLOR].value.c, 0);
	}
	else
	{
		annoDrawText(s, as->currentContext, x, y,
					 ad->opt[ANNO_DISPLAY_OPTION_TEXT_STRINGS].value.list.
					 value[as->stringIndex].s, "Sans", 24, 0, 0,
					 ad->opt[ANNO_DISPLAY_OPTION_FILL_COLOR].value.c,
					 ad->opt[ANNO_DISPLAY_OPTION_FILL_COLOR].value.c, 0);

		as->stringIndex++;
		as->stringIndex %=
				ad->opt[ANNO_DISPLAY_OPTION_TEXT_STRINGS].value.list.nValue;
	}

	damageScreen(s);

	as->firstX = 0;				// Force damageScreen on PaintScreen

	return TRUE;
}

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

	ANNO_SCREEN(s);

	UNWRAP(as, s, paintScreen);
	status = (*s->paintScreen) (s, sAttrib, region, output, mask);
	WRAP(as, s, paintScreen, annoPaintScreen);

	if (status && as->content[as->currentContext] && region->numRects)
	{
		BoxPtr pBox;
		int nBox;

		glPushMatrix();

		prepareXCoords(s, output, -DEFAULT_Z_CAMERA);

		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glEnable(GL_BLEND);

		enableTexture(s, &as->texture[as->currentContext],
					  COMP_TEXTURE_FILTER_FAST);

		pBox = region->rects;
		nBox = region->numRects;

		glBegin(GL_QUADS);

		while (nBox--)
		{
			glTexCoord2f(COMP_TEX_COORD_X
						 (&as->texture[as->currentContext].matrix, pBox->x1),
						 COMP_TEX_COORD_Y(&as->texture[as->currentContext].
										  matrix, pBox->y2));
			glVertex2i(pBox->x1, pBox->y2);
			glTexCoord2f(COMP_TEX_COORD_X
						 (&as->texture[as->currentContext].matrix, pBox->x2),
						 COMP_TEX_COORD_Y(&as->texture[as->currentContext].
										  matrix, pBox->y2));
			glVertex2i(pBox->x2, pBox->y2);
			glTexCoord2f(COMP_TEX_COORD_X
						 (&as->texture[as->currentContext].matrix, pBox->x2),
						 COMP_TEX_COORD_Y(&as->texture[as->currentContext].
										  matrix, pBox->y1));
			glVertex2i(pBox->x2, pBox->y1);
			glTexCoord2f(COMP_TEX_COORD_X
						 (&as->texture[as->currentContext].matrix, pBox->x1),
						 COMP_TEX_COORD_Y(&as->texture[as->currentContext].
										  matrix, pBox->y1));
			glVertex2i(pBox->x1, pBox->y1);

			pBox++;
		}

		glEnd();

		disableTexture(s, &as->texture[as->currentContext]);

		glDisable(GL_BLEND);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);

		glPopMatrix();
	}

	if (as->firstX != -1 && !as->isDrawing)
	{
		as->firstX = -1;
		as->firstY = -1;
		damageScreen(s);
	}

	return status;
}

static void annoHandleMotionEvent(CompScreen * s, int xRoot, int yRoot)
{
	ANNO_SCREEN(s);

	if (as->grabIndex)
	{
		if (as->eraseMode)
		{
			static unsigned short color[] = { 0, 0, 0, 0 };

			annoDrawLine(s, as->currentContext,
						 annoLastPointerX, annoLastPointerY,
						 xRoot, yRoot, 20.0, color);
		}
		else
		{
			char *currentTool;

			ANNO_DISPLAY(s->display);

			currentTool = ad->opt[ANNO_DISPLAY_OPTION_TOOL].value.s;

			if (strcmp(currentTool, "svg") == 0)
			{
				/*annoDrawSvg (s, as->currentContext,
				   ad->opt[ANNO_DISPLAY_OPTION_SVG_FILE].value.s,
				   xRoot, yRoot,
				   0.0, 1.0, 1.0); */
			}
			else if (as->tool == ANNO_TOOL_PAINTBRUSH)
			{
				annoDrawLine(s, as->currentContext,
							 annoLastPointerX, annoLastPointerY,
							 xRoot, yRoot,
							 ad->opt[ANNO_DISPLAY_OPTION_LINE_WIDTH].value.f,
							 ad->opt[ANNO_DISPLAY_OPTION_FILL_COLOR].value.c);
			}
		}

		annoLastPointerX = xRoot;
		annoLastPointerY = yRoot;
	}
}

static void annoHandleEvent(CompDisplay * d, XEvent * event)
{
	CompScreen *s;

	ANNO_DISPLAY(d);

	switch (event->type)
	{
	case MotionNotify:
		s = findScreenAtDisplay(d, event->xmotion.root);
		if (s)
			annoHandleMotionEvent(s, d->pointerX, d->pointerY);
		break;
	case EnterNotify:
	case LeaveNotify:
		s = findScreenAtDisplay(d, event->xcrossing.root);
		if (s)
			annoHandleMotionEvent(s, d->pointerX, d->pointerY);
	default:
		break;
	}

	UNWRAP(ad, d, handleEvent);
	(*d->handleEvent) (d, event);
	WRAP(ad, d, handleEvent, annoHandleEvent);
}

static void annoDisplayInitOptions(AnnoDisplay * ad)
{
	CompOption *o;

	o = &ad->opt[ANNO_DISPLAY_OPTION_INITIATE];
	o->advanced = False;
	o->name = "initiate";
	o->group = N_("Tools");
	o->subGroup = N_("Other");
	o->displayHints = "";
	o->shortDesc = N_("Initiate");
	o->longDesc = N_("Initiate Annotate drawing.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = annoInitiate;
	o->value.action.terminate = annoTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeButton;
	o->value.action.state = CompActionStateInitButton;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.button.modifiers = ANNO_INITIATE_BUTTON_MODIFIERS_DEFAULT;
	o->value.action.button.button = ANNO_INITIATE_BUTTON_DEFAULT;

	o = &ad->opt[ANNO_DISPLAY_OPTION_DRAW];
	o->advanced = False;
	o->name = "draw";
	o->group = N_("Tools");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Draw");
	o->longDesc = N_("Draw using tool.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = annoDraw;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = 0;
	o->value.action.state = 0;

	o = &ad->opt[ANNO_DISPLAY_OPTION_TRANSFORM];
	o->advanced = False;
	o->name = "transform";
	o->group = N_("Style");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Transform the canvas");
	o->longDesc = N_("Transform the canvas, also used to save and restore.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = annoCanvasTransform;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = 0;
	o->value.action.state = 0;

	o = &ad->opt[ANNO_DISPLAY_OPTION_PRELOAD];
	o->advanced = False;
	o->name = "preload";
	o->group = N_("Style");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Preload a svg");
	o->longDesc = N_("Preload a svg.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = annoLoadSvg;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = 0;
	o->value.action.state = 0;

	o = &ad->opt[ANNO_DISPLAY_OPTION_SWITCH_CONTEXT];
	o->advanced = False;
	o->name = "switch_context";
	o->group = N_("Style");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Switch to another canvas");
	o->longDesc = N_("Switch to another canvas for drawing.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = annoCanvasSwitchContext;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = 0;
	o->value.action.state = 0;

	o = &ad->opt[ANNO_DISPLAY_OPTION_ERASE];
	o->advanced = False;
	o->name = "erase";
	o->group = N_("Tools");
	o->subGroup = N_("Eraser");
	o->displayHints = "";
	o->shortDesc = N_("Initiate erase");
	o->longDesc = N_("Initiate Annotate erasing.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = annoEraseInitiate;
	o->value.action.terminate = annoTerminate;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeButton;
	o->value.action.state = CompActionStateInitButton;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.button.modifiers = ANNO_ERASE_BUTTON_MODIFIERS_DEFAULT;
	o->value.action.button.button = ANNO_ERASE_BUTTON_DEFAULT;

	o = &ad->opt[ANNO_DISPLAY_OPTION_CLEAR];
	o->advanced = False;
	o->name = "clear";
	o->group = N_("Tools");
	o->subGroup = N_("Clear");
	o->displayHints = "";
	o->shortDesc = N_("Clear");
	o->longDesc = N_("Clear");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = annoClear;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state = CompActionStateInitEdge;
	o->value.action.state |= CompActionStateInitButton;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.key.modifiers = ANNO_CLEAR_KEY_MODIFIERS_DEFAULT;
	o->value.action.key.keysym = XStringToKeysym(ANNO_CLEAR_KEY_DEFAULT);

	o = &ad->opt[ANNO_DISPLAY_OPTION_FILL_COLOR];
	o->advanced = False;
	o->name = "fill_color";
	o->group = N_("Style");
	o->subGroup = N_("Colors");
	o->displayHints = "";
	o->shortDesc = N_("Annotate Fill Color");
	o->longDesc = N_("Fill color for Annotations.");
	o->type = CompOptionTypeColor;
	o->value.c[0] = ANNO_FILL_COLOR_RED_DEFAULT;
	o->value.c[1] = ANNO_FILL_COLOR_GREEN_DEFAULT;
	o->value.c[2] = ANNO_FILL_COLOR_BLUE_DEFAULT;
	o->value.c[3] = 0xffff;

	o = &ad->opt[ANNO_DISPLAY_OPTION_STROKE_COLOR];
	o->advanced = False;
	o->name = "stroke_color";
	o->group = N_("Style");
	o->subGroup = N_("Colors");
	o->displayHints = "";
	o->shortDesc = N_("Annotate Stroke Color");
	o->longDesc = N_("Stroke color for Annotations.");
	o->type = CompOptionTypeColor;
	o->value.c[0] = ANNO_STROKE_COLOR_RED_DEFAULT;
	o->value.c[1] = ANNO_STROKE_COLOR_GREEN_DEFAULT;
	o->value.c[2] = ANNO_STROKE_COLOR_BLUE_DEFAULT;
	o->value.c[3] = 0xffff;

	o = &ad->opt[ANNO_DISPLAY_OPTION_LINE_WIDTH];
	o->advanced = False;
	o->name = "line_width";
	o->group = N_("Style");
	o->subGroup = N_("Size");
	o->displayHints = "";
	o->shortDesc = N_("Line width");
	o->longDesc = N_("Line width for Annotations.");
	o->type = CompOptionTypeFloat;
	o->value.f = ANNO_LINE_WIDTH_DEFAULT;
	o->rest.f.min = ANNO_LINE_WIDTH_MIN;
	o->rest.f.max = ANNO_LINE_WIDTH_MAX;
	o->rest.f.precision = ANNO_LINE_WIDTH_PRECISION;

	o = &ad->opt[ANNO_DISPLAY_OPTION_STROKE_WIDTH];
	o->advanced = False;
	o->name = "stroke_width";
	o->group = N_("Style");
	o->subGroup = N_("Size");
	o->displayHints = "";
	o->shortDesc = N_("Stroke width");
	o->longDesc = N_("Stroke width for Annotations.");
	o->type = CompOptionTypeFloat;
	o->value.f = ANNO_STROKE_WIDTH_DEFAULT;
	o->rest.f.min = ANNO_STROKE_WIDTH_MIN;
	o->rest.f.max = ANNO_STROKE_WIDTH_MAX;
	o->rest.f.precision = ANNO_STROKE_WIDTH_PRECISION;

	o = &ad->opt[ANNO_DISPLAY_OPTION_SVG_FILE];
	o->advanced = False;
	o->name = "svg_file";
	o->group = N_("Style");
	o->subGroup = N_("Files");
	o->displayHints = "";
	o->shortDesc = N_("SVG (custom brush) filename");
	o->longDesc = N_("SVG (custom brush) filename.");
	o->type = CompOptionTypeString;
	o->value.s = strdup("");
	o->rest.s.string = 0;
	o->rest.s.nString = 0;

	o = &ad->opt[ANNO_DISPLAY_OPTION_TOOL];
	o->advanced = False;
	o->name = "tool";
	o->group = N_("Style");
	o->subGroup = N_("Files");
	o->displayHints = "";
	o->shortDesc = N_("SVG (custom brush) filename");
	o->longDesc = N_("SVG (custom brush) filename.");
	o->type = CompOptionTypeString;
	o->value.s = strdup("");
	o->rest.s.string = 0;
	o->rest.s.nString = 0;

	o = &ad->opt[ANNO_DISPLAY_OPTION_USE_PAINTBRUSH];
	o->advanced = False;
	o->name = "use_pb";
	o->group = N_("Tools");
	o->subGroup = N_("Paintbrush");
	o->displayHints = "";
	o->shortDesc = N_("Use Paintbrush Tool");
	o->longDesc = N_("Click and drag the paintbrush to draw figures.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = annoUsePaintbrush;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state = CompActionStateInitButton;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.key.modifiers =
			ANNO_SWITCH_PAINTBRUSH_KEY_MODIFIERS_DEFAULT;
	o->value.action.key.keysym =
			XStringToKeysym(ANNO_SWITCH_PAINTBRUSH_KEY_DEFAULT);

	o = &ad->opt[ANNO_DISPLAY_OPTION_USE_LINE];
	o->advanced = False;
	o->name = "use_line";
	o->group = N_("Tools");
	o->subGroup = N_("Line");
	o->displayHints = "";
	o->shortDesc = N_("Use Line Tool");
	o->longDesc = N_("Draw lines by clicking and dragging.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = annoUseLine;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state = CompActionStateInitButton;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.key.modifiers = ANNO_SWITCH_LINE_KEY_MODIFIERS_DEFAULT;
	o->value.action.key.keysym =
			XStringToKeysym(ANNO_SWITCH_LINE_KEY_DEFAULT);

	o = &ad->opt[ANNO_DISPLAY_OPTION_USE_ARROW];
	o->advanced = False;
	o->name = "use_line";
	o->group = N_("Tools");
	o->subGroup = N_("Arrow");
	o->displayHints = "";
	o->shortDesc = N_("Use Arrow Tool");
	o->longDesc = N_("Draw arrows by clicking and dragging.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = annoUseArrow;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeKey;
	o->value.action.state = CompActionStateInitButton;
	o->value.action.state |= CompActionStateInitKey;
	o->value.action.key.modifiers = ANNO_SWITCH_ARROW_KEY_MODIFIERS_DEFAULT;
	o->value.action.key.keysym =
			XStringToKeysym(ANNO_SWITCH_ARROW_KEY_DEFAULT);

	o = &ad->opt[ANNO_DISPLAY_OPTION_TEXT_STRINGS];
	o->advanced = False;
	o->name = "text_strings";
	o->group = N_("Tools");
	o->subGroup = N_("Text");
	o->displayHints = "";
	o->shortDesc = N_("Text Strings");
	o->longDesc =
			N_
			("Strings to be displayed when using the put string tool, in order entered (not in order on screen).");
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = 0;
	o->value.list.value = NULL;
	o->rest.s.string = 0;
	o->rest.s.nString = 0;

	o = &ad->opt[ANNO_DISPLAY_OPTION_PUT_TEXT];
	o->advanced = False;
	o->name = "put_text";
	o->group = N_("Tools");
	o->subGroup = N_("Text");
	o->displayHints = "";
	o->shortDesc = N_("Put Text");
	o->longDesc = N_("Put text at the mouse cursor.");
	o->type = CompOptionTypeAction;
	o->value.action.initiate = annoPutText;
	o->value.action.terminate = 0;
	o->value.action.bell = FALSE;
	o->value.action.edgeMask = 0;
	o->value.action.type = CompBindingTypeNone;	//CompBindingTypeKey;
	o->value.action.state = CompActionStateInitButton;
	o->value.action.state |= CompActionStateInitKey;
//  o->value.action.key.modifiers = ANNO_PUT_TEXT_KEY_MODIFIERS_DEFAULT;
//  o->value.action.key.keysym = XStringToKeysym(ANNO_PUT_TEXT_KEY_DEFAULT);
}

static CompOption *annoGetDisplayOptions(CompDisplay * display, int *count)
{
	if (display)
	{
		ANNO_DISPLAY(display);

		*count = NUM_OPTIONS(ad);
		return ad->opt;
	}
	else
	{
		AnnoDisplay *ad = malloc(sizeof(AnnoDisplay));

		annoDisplayInitOptions(ad);
		*count = NUM_OPTIONS(ad);
		return ad->opt;
	}
}

static Bool
annoSetDisplayOption(CompDisplay * display,
					 char *name, CompOptionValue * value)
{
	CompOption *o;
	int index;

	ANNO_DISPLAY(display);

	o = compFindOption(ad->opt, NUM_OPTIONS(ad), name, &index);
	if (!o)
		return FALSE;

	switch (index)
	{
	case ANNO_DISPLAY_OPTION_INITIATE:
	case ANNO_DISPLAY_OPTION_ERASE:
	case ANNO_DISPLAY_OPTION_CLEAR:
	case ANNO_DISPLAY_OPTION_USE_PAINTBRUSH:
	case ANNO_DISPLAY_OPTION_USE_LINE:
	case ANNO_DISPLAY_OPTION_USE_ARROW:
		if (setDisplayAction(display, o, value))
			return TRUE;
		break;
	case ANNO_DISPLAY_OPTION_FILL_COLOR:
	case ANNO_DISPLAY_OPTION_STROKE_COLOR:
		if (compSetColorOption(o, value))
			return TRUE;
		break;
	case ANNO_DISPLAY_OPTION_LINE_WIDTH:
	case ANNO_DISPLAY_OPTION_STROKE_WIDTH:
		if (compSetFloatOption(o, value))
			return TRUE;
		break;
	case ANNO_DISPLAY_OPTION_TOOL:
	case ANNO_DISPLAY_OPTION_SVG_FILE:
		if (compSetStringOption(o, value))
			return TRUE;
		break;
	case ANNO_DISPLAY_OPTION_TEXT_STRINGS:
		if (compSetOptionList(o, value))
			return TRUE;
		break;
	case ANNO_DISPLAY_OPTION_PUT_TEXT:
		if (setDisplayAction(display, o, value))
			return TRUE;
		break;
	default:
		break;
	}

	return FALSE;
}

static Bool annoInitDisplay(CompPlugin * p, CompDisplay * d)
{
	AnnoDisplay *ad;

	ad = malloc(sizeof(AnnoDisplay));
	if (!ad)
		return FALSE;

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

	WRAP(ad, d, handleEvent, annoHandleEvent);

	annoDisplayInitOptions(ad);

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

	return TRUE;
}

static void annoFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	ANNO_DISPLAY(d);

	freeScreenPrivateIndex(d, ad->screenPrivateIndex);

	UNWRAP(ad, d, handleEvent);

	free(ad);
}

static Bool annoInitScreen(CompPlugin * p, CompScreen * s)
{
	AnnoScreen *as;
	int i;

	ANNO_DISPLAY(s->display);

	as = malloc(sizeof(AnnoScreen));
	if (!as)
		return FALSE;

	as->grabIndex = 0;
	as->tool = ANNO_TOOL_PAINTBRUSH;
	as->firstX = -1;
	as->firstY = -1;
	as->isDrawing = FALSE;
	as->stringIndex = 0;

	for (i = 0; i < ANNO_NUMBER_CONTEXTS; i++)
	{
		as->surface[i] = NULL;
		as->pixmap[i] = None;
		as->cairo[i] = NULL;
		as->content[i] = FALSE;
		initTexture(s, &as->texture[i]);
	}

	as->currentContext = 0;

	addScreenAction(s, &ad->opt[ANNO_DISPLAY_OPTION_INITIATE].value.action);
	addScreenAction(s, &ad->opt[ANNO_DISPLAY_OPTION_ERASE].value.action);
	addScreenAction(s, &ad->opt[ANNO_DISPLAY_OPTION_CLEAR].value.action);
	addScreenAction(s,
					&ad->opt[ANNO_DISPLAY_OPTION_USE_PAINTBRUSH].value.
					action);
	addScreenAction(s, &ad->opt[ANNO_DISPLAY_OPTION_USE_LINE].value.action);
	addScreenAction(s, &ad->opt[ANNO_DISPLAY_OPTION_USE_ARROW].value.action);
	addScreenAction(s, &ad->opt[ANNO_DISPLAY_OPTION_PUT_TEXT].value.action);

	WRAP(as, s, paintScreen, annoPaintScreen);

	s->privates[ad->screenPrivateIndex].ptr = as;

	return TRUE;
}

static void annoFiniScreen(CompPlugin * p, CompScreen * s)
{
	int i;

	ANNO_SCREEN(s);
	ANNO_DISPLAY(s->display);

	for (i = 0; i < ANNO_NUMBER_CONTEXTS; i++)
	{
		if (as->cairo[i])
			cairo_destroy(as->cairo[i]);

		if (as->surface[i])
			cairo_surface_destroy(as->surface[i]);

		finiTexture(s, &as->texture[i]);

		if (as->pixmap[i])
			XFreePixmap(s->display->display, as->pixmap[i]);
	}

	removeScreenAction(s,
					   &ad->opt[ANNO_DISPLAY_OPTION_INITIATE].value.action);
	removeScreenAction(s, &ad->opt[ANNO_DISPLAY_OPTION_ERASE].value.action);
	removeScreenAction(s, &ad->opt[ANNO_DISPLAY_OPTION_CLEAR].value.action);
	removeScreenAction(s,
					   &ad->opt[ANNO_DISPLAY_OPTION_USE_PAINTBRUSH].value.
					   action);
	removeScreenAction(s,
					   &ad->opt[ANNO_DISPLAY_OPTION_USE_LINE].value.action);
	removeScreenAction(s,
					   &ad->opt[ANNO_DISPLAY_OPTION_USE_ARROW].value.action);
	removeScreenAction(s,
					   &ad->opt[ANNO_DISPLAY_OPTION_PUT_TEXT].value.action);

	UNWRAP(as, s, paintScreen);

	free(as);
}

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

	return TRUE;
}

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

static CompPluginVTable annoVTable = {
	"annotate",
	N_("Annotate"),
	N_("Annotate plugin"),
	annoInit,
	annoFini,
	annoInitDisplay,
	annoFiniDisplay,
	annoInitScreen,
	annoFiniScreen,
	0,							/* InitWindow */
	0,							/* FiniWindow */
	annoGetDisplayOptions,
	annoSetDisplayOption,
	0,							/* GetScreenOptions */
	0,							/* SetScreenOption */
	0,							/* Deps */
	0,							/* nDeps */
	0,							/* Features */
	0,							/* nFeatures */
	BERYL_ABI_INFO,
	"beryl-plugins",
	"misc",
	0,
	0,
	False,
};

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


syntax highlighted by Code2HTML, v. 0.9.1