/**************************************************************

cmpack_entity.cpp (C-Munipack project)
Object which holds data for a time scale
Copyright (C) 2011 David Motl, dmotl@volny.cz

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

$Id: cmpack_entity.cpp,v 1.4 2016/05/15 14:52:38 dmotl Exp $

**************************************************************/
#define _USE_MATH_DEFINES

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "cmpack_entity.h"

// Round number to the integer with limit checking
static int RoundToInt(double x)
{
	if (x>INT_MAX)
		return INT_MAX;
	else if (x<INT_MIN)
		return INT_MIN;
	else if (x<0)
		return (int)(x-0.5);
	else
		return (int)(x+0.5);
}

/* Physical units -> display units */
static gdouble x_to_view(CmpackEntityDrawContext *ctx, gdouble x)
{
	return ((!ctx->reverse_x ? 1.0 : -1.0) * x - ctx->offset_x) / ctx->scale_x;
}

/* Physical units -> display units */
static gdouble y_to_view(CmpackEntityDrawContext *ctx, gdouble y)
{
	return (ctx->offset_y - (!ctx->reverse_y ? -1.0 : 1.0) * y) / ctx->scale_y;
}

/*--------------------   ENTITY BASE CLASS   ----------------------------*/

G_DEFINE_TYPE(CmpackEntity, cmpack_entity, G_TYPE_OBJECT)

/* Class initialization */
static void cmpack_entity_class_init(CmpackEntityClass *klass)
{
}

/* Instance initialization */
static void cmpack_entity_init(CmpackEntity *data) 
{
}

void cmpack_entity_draw_entity(CmpackEntity *self, CmpackEntityDrawContext *context)
{
	g_return_if_fail(CMPACK_IS_ENTITY(self));

	if (CMPACK_ENTITY_GET_CLASS(self)->draw_entity)
		CMPACK_ENTITY_GET_CLASS(self)->draw_entity(self, context);
}

void cmpack_entity_draw_label(CmpackEntity *self, CmpackEntityDrawContext *context)
{
	g_return_if_fail(CMPACK_IS_ENTITY(self));

	if (CMPACK_ENTITY_GET_CLASS(self)->draw_label)
		CMPACK_ENTITY_GET_CLASS(self)->draw_label(self, context);
}

void cmpack_entity_move(CmpackEntity *self, gdouble x, gdouble y)
{
	g_return_if_fail(CMPACK_IS_ENTITY(self));

	if (CMPACK_ENTITY_GET_CLASS(self)->move)
		CMPACK_ENTITY_GET_CLASS(self)->move(self, x, y);
}

void cmpack_entity_resize(CmpackEntity *self, gdouble width, gdouble height)
{
	g_return_if_fail(CMPACK_IS_ENTITY(self));

	if (CMPACK_ENTITY_GET_CLASS(self)->resize)
		CMPACK_ENTITY_GET_CLASS(self)->resize(self, width, height);
}

void cmpack_entity_set_custom_color(CmpackEntity *self, const GdkColor *color)
{
	g_return_if_fail(CMPACK_IS_ENTITY(self));

	self->custom_color = color;
}

const GdkColor *cmpack_entity_custom_color(CmpackEntity *self)
{
	g_return_val_if_fail(CMPACK_IS_ENTITY(self), NULL);

	return self->custom_color;
}

/*--------------------   CIRCLE ENTITY   ----------------------------*/

G_DEFINE_TYPE(CmpackCircle, cmpack_circle, CMPACK_TYPE_ENTITY)

static void cmpack_circle_draw(CmpackEntity *self, CmpackEntityDrawContext *context);
static void cmpack_circle_move(CmpackEntity *self, gdouble x, gdouble y);
static void cmpack_circle_resize(CmpackEntity *self, gdouble width, gdouble height);

/* Class initialization */
static void cmpack_circle_class_init(CmpackCircleClass *klass)
{
	CmpackEntityClass *entity_class = (CmpackEntityClass *)klass;
	entity_class->draw_entity = cmpack_circle_draw;
	entity_class->move = cmpack_circle_move;
	entity_class->resize = cmpack_circle_resize;
}

/* Instance initialization */
static void cmpack_circle_init(CmpackCircle *data) 
{
	data->color = CMPACK_COLOR_DEFAULT;
}

/* Move an object */
static void cmpack_circle_move(CmpackEntity *self, gdouble x, gdouble y)
{
	g_return_if_fail(CMPACK_IS_CIRCLE (self));

	CmpackCircle *object = CMPACK_CIRCLE(self);
	object->xphys = x;
	object->yphys = y;
}

/* Change a radius of a circle */
static void cmpack_circle_resize(CmpackEntity *self, gdouble w, gdouble h)
{
	g_return_if_fail(CMPACK_IS_CIRCLE (self));

	CmpackCircle *object = CMPACK_CIRCLE(self);
	object->width = w;
	object->height = h;
}

/* Draw a circle */
static void cmpack_circle_draw(CmpackEntity *self, CmpackEntityDrawContext *ctx)
{
	gint x1, x2, y1, y2;

	g_return_if_fail(CMPACK_IS_CIRCLE (self));

	CmpackCircle	*obj = CMPACK_CIRCLE(self);
	GdkGC			*gc = ctx->gc;
	GdkDrawable		*drawable = ctx->drawable;

	if (!ctx->reverse_x) {
		x1 = RoundToInt(x_to_view(ctx, obj->xphys));
		x2 = RoundToInt(x_to_view(ctx, obj->xphys + obj->width));
	} else {
		x1 = RoundToInt(x_to_view(ctx, obj->xphys + obj->width));
		x2 = RoundToInt(x_to_view(ctx, obj->xphys));
	}
	if (!ctx->reverse_y) {
		y1 = RoundToInt(y_to_view(ctx, obj->yphys));
		y2 = RoundToInt(y_to_view(ctx, obj->yphys + obj->height));
	} else {
		y1 = RoundToInt(y_to_view(ctx, obj->yphys + obj->height));
		y2 = RoundToInt(y_to_view(ctx, obj->yphys));
	}
	if (x2 >= ctx->canvas_rc.x && x1 <= ctx->canvas_rc.x + ctx->canvas_rc.width &&
		y2 >= ctx->canvas_rc.y && y1 <= ctx->canvas_rc.y + ctx->canvas_rc.height) {
			if (self->custom_color)
				gdk_gc_set_foreground(gc, self->custom_color);
			else
				gdk_gc_set_foreground(gc, ctx->fg_color[obj->color]);
			if (obj->filled) {
				gdk_draw_arc(drawable, gc, TRUE, x1, y1, x2-x1, y2-y1, 0, 64*360);
				gdk_gc_set_foreground(gc, ctx->bg_color);
			}
			gdk_draw_arc(drawable, gc, FALSE, x1, y1, x2-x1, y2-y1, 0, 64*360);
	}
}

CmpackEntity *cmpack_new_circle(gdouble left, gdouble top, gdouble width, gdouble height, CmpackColor color, gboolean filled)
{
	CmpackCircle *object = (CmpackCircle*)g_object_new(CMPACK_TYPE_CIRCLE, NULL);

	object->filled = filled;
	object->color = color;
	object->width = width;
	object->height = height;
	object->xphys = left;
	object->yphys = top;

	return CMPACK_ENTITY(object);
}

/*  ------------------------   QUADRATIC POLYNOMIAL CURVE  ----------------------------------- */

G_DEFINE_TYPE(CmpackQPCurve, cmpack_qpcurve, CMPACK_TYPE_ENTITY)

static void cmpack_qpcurve_draw(CmpackEntity *self, CmpackEntityDrawContext *context);
static void cmpack_qpcurve_finalize(GObject *self);

/* Class initialization */
static void cmpack_qpcurve_class_init(CmpackQPCurveClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS( klass );
	gobject_class->finalize = cmpack_qpcurve_finalize;

	CmpackEntityClass *entity_class = (CmpackEntityClass *)klass;
	entity_class->draw_entity = cmpack_qpcurve_draw;
}

/* Instance initialization */
static void cmpack_qpcurve_init(CmpackQPCurve *data) 
{
	data->color = CMPACK_COLOR_DEFAULT;
	data->data_changed = TRUE;
}

static void cmpack_qpcurve_finalize( GObject *self )
{
	g_return_if_fail(CMPACK_IS_QPCURVE (self));

	CmpackQPCurve *object = CMPACK_QPCURVE(self);
	g_list_foreach(object->pts, (GFunc)g_free, NULL);
	g_list_free(object->pts);
	object->pts = NULL;
	g_free(object->points);
	object->points = NULL;
	object->npoints = 0;
	 
	G_OBJECT_CLASS( cmpack_qpcurve_parent_class )->finalize(self);
}

/* Compute point */
static void track(CmpackQPCurve *obj, double t, double *x, double *y)
{
	*x = obj->coeff[0] + obj->coeff[1] * (t) + obj->coeff[2] * (t) * (t);
	*y = obj->coeff[3] + obj->coeff[4] * (t) + obj->coeff[5] * (t) * (t);
}

typedef struct _CmpackQPCurveTrackPoint {
	gdouble t, u, v;
	gint x, y;
} CmpackQPCurveTrackPoint;

static CmpackQPCurveTrackPoint *new_track_point(CmpackQPCurve *obj, gdouble t)
{
	CmpackQPCurveTrackPoint *p = (CmpackQPCurveTrackPoint*)g_malloc(sizeof(CmpackQPCurveTrackPoint));
	p->t = t;
	track(obj, t, &p->u, &p->v);
	return p;
}

static void check_and_divide(CmpackQPCurve *obj, GList *point, gdouble kx, gdouble ky)
{
	CmpackQPCurveTrackPoint *start = (CmpackQPCurveTrackPoint*)point->data, *end = (CmpackQPCurveTrackPoint*)point->next->data;

	gdouble t0 = start->t, t1 = end->t;
	if (fabs(t0-t1) > 1e-99) {
		gdouble r2 = (pow(obj->coeff[2]*kx, 2) + pow(obj->coeff[5]*ky, 2)) * pow(0.5*(t0-t1), 4);
		if (r2 > 1) {
			point = g_list_insert_before(point, point->next, new_track_point(obj, (t0+t1)/2));
			GList *mid = point->next;
			check_and_divide(obj, point, kx, ky);
			check_and_divide(obj, mid, kx, ky);
		}
	}
}

/* Draw a qpcurve */
static void cmpack_qpcurve_draw(CmpackEntity *self, CmpackEntityDrawContext *ctx)
{
	g_return_if_fail(CMPACK_IS_QPCURVE (self));

	CmpackQPCurve	*obj = CMPACK_QPCURVE(self);
	GdkGC			*gc = ctx->gc;
	GdkDrawable		*drawable = ctx->drawable;
	gint			x1, x2, y1, y2, x0, y0;
	gdouble			fx, fy;

	if (obj->data_changed || ctx->scale_x != obj->scale_x || ctx->reverse_x != obj->reverse_x || ctx->scale_y != obj->scale_y || ctx->reverse_y != obj->reverse_y) {
		if (ctx->scale_x!=0 && ctx->scale_y!=0) {
			g_list_foreach(obj->pts, (GFunc)g_free, NULL);
			g_list_free(obj->pts);
			memset(&obj->brect, 0, sizeof(GdkRectangle));
			obj->pts = g_list_prepend(g_list_prepend(NULL, new_track_point(obj, obj->t1)), new_track_point(obj, obj->t0));
			check_and_divide(obj, obj->pts, 1.0/ctx->scale_x, 1.0/ctx->scale_y);

			int n = g_list_length(obj->pts);
			if (n != obj->npoints) {
				g_free(obj->points);
				if (n > 0)
					obj->points = (GdkPoint*)g_malloc(n*sizeof(GdkPoint));
				else
					obj->points = NULL;
				obj->npoints = n;
			} 
						
			CmpackQPCurveTrackPoint *p0 = (CmpackQPCurveTrackPoint*)obj->pts->data;
			obj->origin.x = RoundToInt(x_to_view(ctx, p0->u));
			obj->origin.y = RoundToInt(y_to_view(ctx, p0->v));
			x1 = x2 = y1 = y2 = p0->x = p0->y = 0;
			for (GList *ptr = obj->pts->next; ptr != NULL; ptr = ptr->next) {
				CmpackQPCurveTrackPoint *p = (CmpackQPCurveTrackPoint*)ptr->data;
				p->x = RoundToInt(x_to_view(ctx, p->u)) - obj->origin.x;
				p->y = RoundToInt(y_to_view(ctx, p->v)) - obj->origin.y;
				if (p->x < x1)
					x1 = p->x;
				if (p->x > x2)
					x2 = p->x;
				if (p->y < y1)
					y1 = p->y;
				if (p->y > y2)
					y2 = p->y;
			}
			obj->brect.x = x1;
			obj->brect.y = y1;
			obj->brect.width = (x2-x1+1);
			obj->brect.height = (y2-y1+1);
			obj->scale_x = ctx->scale_x;
			obj->reverse_x = ctx->reverse_x;
			obj->scale_y = ctx->scale_y;
			obj->reverse_y = ctx->reverse_y;
		}
		obj->data_changed = FALSE;
	}
	if (ctx->scale_x==0 || ctx->scale_y==0)
		return;

	track(obj, obj->t0, &fx, &fy);
	x0 = RoundToInt(x_to_view(ctx, fx));
	y0 = RoundToInt(y_to_view(ctx, fy));

	x1 = obj->brect.x + x0;
	y1 = obj->brect.y + y0;
	x2 = x1 + obj->brect.width;
	y2 = y1 + obj->brect.height;
	if (x2 >= ctx->canvas_rc.x && x1 <= ctx->canvas_rc.x + ctx->canvas_rc.width &&
		y2 >= ctx->canvas_rc.y && y1 <= ctx->canvas_rc.y + ctx->canvas_rc.height) {
			if (obj->pts && obj->npoints>0 && obj->points) {
				int i = 0;
				for (GList *ptr = obj->pts; ptr != NULL; ptr = ptr->next) {
					obj->points[i].x = x0 + ((CmpackQPCurveTrackPoint*)ptr->data)->x;
					obj->points[i].y = y0 + ((CmpackQPCurveTrackPoint*)ptr->data)->y;
					i++;
				}
				if (self->custom_color)
					gdk_gc_set_foreground(gc, self->custom_color);
				else
					gdk_gc_set_foreground(gc, ctx->fg_color[obj->color]);
				gdk_draw_lines(drawable, gc, obj->points, obj->npoints);
			}
	}
}

CmpackEntity *cmpack_new_qpcurve(const gdouble *coeff, gdouble t0, gdouble t1, CmpackColor color)
{
	CmpackQPCurve *obj = (CmpackQPCurve*)g_object_new(CMPACK_TYPE_QPCURVE, NULL);

	obj->color = color;
	obj->t0 = t0;
	obj->t1 = t1;
	memcpy(obj->coeff, coeff, 6*sizeof(gdouble));

	obj->data_changed = TRUE;

	return CMPACK_ENTITY(obj);
}

/*  -----------------------------   MARK  -------------------------------------- */

G_DEFINE_TYPE(CmpackMark, cmpack_mark, CMPACK_TYPE_ENTITY)

static void cmpack_mark_draw(CmpackEntity *self, CmpackEntityDrawContext *context);
static void cmpack_mark_move(CmpackEntity *self, gdouble x, gdouble y);

/* Class initialization */
static void cmpack_mark_class_init(CmpackMarkClass *klass)
{
	CmpackEntityClass *entity_class = (CmpackEntityClass *)klass;
	entity_class->draw_entity = cmpack_mark_draw;
	entity_class->move = cmpack_mark_move;
}

/* Instance initialization */
static void cmpack_mark_init(CmpackMark *data) 
{
	data->color = CMPACK_COLOR_DEFAULT;
	data->data_changed = TRUE;
}

/* Move an object */
static void cmpack_mark_move(CmpackEntity *self, gdouble x, gdouble y)
{
	g_return_if_fail(CMPACK_IS_MARK (self));

	CmpackMark *object = CMPACK_MARK(self);
	if (object->x!=x || object->y!=y) {
		object->x = x;
		object->y = y;
	}
}

/* Draw a mark */
static void cmpack_mark_draw(CmpackEntity *self, CmpackEntityDrawContext *ctx)
{
	g_return_if_fail(CMPACK_IS_MARK (self));

	CmpackMark	*obj = CMPACK_MARK(self);
	GdkGC			*gc = ctx->gc;
	GdkDrawable		*drawable = ctx->drawable;
	gint			x1, x2, y1, y2, x0, y0;

	if (obj->data_changed || ctx->reverse_x != obj->reverse_x || ctx->reverse_y != obj->reverse_y) {
		obj->reverse_x = ctx->reverse_x;
		obj->reverse_y = ctx->reverse_y;
		double tilt = 90.0 - obj->tilt;
		if (!ctx->reverse_x)
			tilt = -tilt;
		if (ctx->reverse_y)
			tilt = 180 - tilt;
		obj->dx = RoundToInt(-8 * sin(tilt/180.0*M_PI));
		obj->dy = RoundToInt(8 * cos(tilt/180.0*M_PI));
		obj->data_changed = FALSE;
	}
	if (ctx->scale_x==0 || ctx->scale_y==0)
		return;

	x0 = RoundToInt(x_to_view(ctx, obj->x));
	y0 = RoundToInt(y_to_view(ctx, obj->y));

	x1 = x0 - abs(obj->dx);
	y1 = y0 - abs(obj->dy);
	x2 = x0 + abs(obj->dx);
	y2 = y0 + abs(obj->dy);
	if (x2 >= ctx->canvas_rc.x && x1 <= ctx->canvas_rc.x + ctx->canvas_rc.width &&
		y2 >= ctx->canvas_rc.y && y1 <= ctx->canvas_rc.y + ctx->canvas_rc.height) {
			if (self->custom_color)
				gdk_gc_set_foreground(gc, self->custom_color);
			else
				gdk_gc_set_foreground(gc, ctx->fg_color[obj->color]);
			gdk_draw_line(drawable, gc, x0 - obj->dx, y0 - obj->dy, x0 + obj->dx, y0 + obj->dy);
	}
}

CmpackEntity *cmpack_new_mark(gdouble x, gdouble y, gdouble tilt, CmpackColor color)
{
	CmpackMark *obj = (CmpackMark*)g_object_new(CMPACK_TYPE_MARK, NULL);

	obj->color = color;
	obj->x = x;
	obj->y = y;
	obj->tilt = tilt;
	obj->data_changed = TRUE;

	return CMPACK_ENTITY(obj);
}

/*  -----------------------------   OBJECT  -------------------------------------- */

G_DEFINE_TYPE(CmpackObject, cmpack_object, CMPACK_TYPE_ENTITY)

static void cmpack_object_finalize(GObject *hamster);
static void cmpack_object_draw_entity(CmpackEntity *self, CmpackEntityDrawContext *context);
static void cmpack_object_draw_label(CmpackEntity *self, CmpackEntityDrawContext *context);
static void cmpack_object_move(CmpackEntity *self, gdouble x, gdouble y);

#define CMPACK_OBJECT_SIZE 4

/* Class initialization */
static void cmpack_object_class_init(CmpackObjectClass *klass)
{
	GObjectClass *gobject_class;

	gobject_class = (GObjectClass *)klass;

	CmpackEntityClass *entity_class = (CmpackEntityClass *)klass;
	entity_class->draw_entity = cmpack_object_draw_entity;
	entity_class->draw_label = cmpack_object_draw_label;
	entity_class->move = cmpack_object_move;
	gobject_class->finalize = cmpack_object_finalize;
}

/* Instance initialization */
static void cmpack_object_init(CmpackObject *data)
{
	data->color = CMPACK_COLOR_DEFAULT;
	data->caption = NULL;
}

/* Instance finalization */
static void cmpack_object_finalize(GObject *object)
{
	CmpackObject *data = CMPACK_OBJECT(object);

	g_free(data->caption);
	data->caption = NULL;

	/* Chain up to the parent class */
	G_OBJECT_CLASS(cmpack_object_parent_class)->finalize(object);
}

/* Move an object */
static void cmpack_object_move(CmpackEntity *self, gdouble x, gdouble y)
{
	g_return_if_fail(CMPACK_IS_OBJECT(self));

	CmpackObject *object = CMPACK_OBJECT(self);
	if (object->x != x || object->y != y) {
		object->x = x;
		object->y = y;
	}
}

/* Draw a object */
static void cmpack_object_draw_entity(CmpackEntity *self, CmpackEntityDrawContext *ctx)
{
	g_return_if_fail(CMPACK_IS_OBJECT(self));

	static const int d = CMPACK_OBJECT_SIZE;

	CmpackObject	*obj = CMPACK_OBJECT(self);
	GdkGC			*gc = ctx->gc;
	GdkDrawable		*drawable = ctx->drawable;
	gint			x1, x2, y1, y2, x0, y0;

	if (ctx->scale_x == 0 || ctx->scale_y == 0)
		return;

	x0 = RoundToInt(x_to_view(ctx, obj->x));
	y0 = RoundToInt(y_to_view(ctx, obj->y));

	x1 = x0 - d;
	y1 = y0 - d;
	x2 = x0 + d;
	y2 = y0 + d;
	if (x2 >= ctx->canvas_rc.x && x1 <= ctx->canvas_rc.x + ctx->canvas_rc.width &&
		y2 >= ctx->canvas_rc.y && y1 <= ctx->canvas_rc.y + ctx->canvas_rc.height) {
			if (self->custom_color)
				gdk_gc_set_foreground(gc, self->custom_color);
			else
				gdk_gc_set_foreground(gc, ctx->fg_color[obj->color]);
			gdk_draw_line(drawable, gc, x0, y0 - d, x0, y0);
			gdk_draw_line(drawable, gc, x0, y0 + d, x0, y0);
			gdk_draw_line(drawable, gc, x0 + d, y0, x0, y0);
			gdk_draw_line(drawable, gc, x0 - d, y0, x0, y0);
	}
}

/* Draw a object */
static void cmpack_object_draw_label(CmpackEntity *self, CmpackEntityDrawContext *ctx)
{
	g_return_if_fail(CMPACK_IS_OBJECT(self));

	static const int d = CMPACK_OBJECT_SIZE;

	CmpackObject	*obj = CMPACK_OBJECT(self);
	gdouble			x0, y0;
	gint			x, y, w, h;

	if (obj->caption) {
		x0 = x_to_view(ctx, obj->x);
		y0 = y_to_view(ctx, obj->y);
		x = RoundToInt(x0) - (d + 1) / 2;
		y = RoundToInt(y0) - (d + 1) / 2;
		if (x + d >= ctx->canvas_rc.x && x <= ctx->canvas_rc.x + ctx->canvas_rc.width &&
			y + d >= ctx->canvas_rc.y && y <= ctx->canvas_rc.y + ctx->canvas_rc.height) {

			pango_layout_set_text(ctx->layout, obj->caption, -1);
			pango_layout_get_pixel_size(ctx->layout, &w, &h);
			w += 4;

			x = RoundToInt(x0 + 0.3 * d);
			if (x + w > ctx->canvas_rc.x + ctx->canvas_rc.width) {
				// If the text is too far to the right, align it to the right border
				x = ctx->canvas_rc.x + ctx->canvas_rc.width - w;
			}
			else if (x < ctx->canvas_rc.x) {
				// If the text is too far to the left, align it to the left border
				x = ctx->canvas_rc.x;
			}

			y = RoundToInt(y0 + 0.3 * d);
			if (y + h > ctx->canvas_rc.y + ctx->canvas_rc.height) {
				// If the text is too far to the bottom, render it above the mark
				y = RoundToInt(y0 - 0.3 * d - h);
			}

			gdk_gc_set_foreground(ctx->gc, ctx->bg_color);
			gdk_draw_layout(ctx->drawable, ctx->gc, x + 3, y + 1, ctx->layout);

			if (self->custom_color)
				gdk_gc_set_foreground(ctx->gc, self->custom_color);
			else
				gdk_gc_set_foreground(ctx->gc, ctx->fg_color[obj->color]);
			gdk_draw_layout(ctx->drawable, ctx->gc, x + 2, y, ctx->layout);
		}
	}
}

CmpackEntity *cmpack_new_object(gdouble x, gdouble y, CmpackColor color, const gchar *caption)
{
	CmpackObject *obj = (CmpackObject*)g_object_new(CMPACK_TYPE_OBJECT, NULL);

	obj->color = color;
	obj->x = x;
	obj->y = y;
	obj->caption = caption ? g_strdup(caption) : NULL;

	return CMPACK_ENTITY(obj);
}
