
#include "WINGsP.h"

#define XDND_COLOR_DATA_TYPE "application/X-color"

const char *WMColorWellDidChangeNotification = "WMColorWellDidChangeNotification";

typedef struct W_ColorWell {
	W_Class widgetClass;
	WMView *view;

	WMView *colorView;

	WMColor *color;

	WMAction *action;
	void *clientData;

	WMPoint ipoint;

	struct {
		unsigned int active:1;
		unsigned int bordered:1;
	} flags;

	WMArray *xdndTypes;
} ColorWell;

static char *_ColorWellActivatedNotification = "_ColorWellActivatedNotification";

static void destroyColorWell(ColorWell * cPtr);
static void paintColorWell(ColorWell * cPtr);

static void handleEvents(XEvent * event, void *data);

static void handleDragEvents(XEvent * event, void *data);

static void handleActionEvents(XEvent * event, void *data);

static void willResizeColorWell(W_ViewDelegate * self, WMView * view, unsigned int *width, unsigned int *height);

W_ViewDelegate _ColorWellViewDelegate = {
	NULL,
	NULL,
	NULL,
	NULL,
	willResizeColorWell
};

static WMArray *dropDataTypes(WMView * self);
static WMDragOperationType wantedDropOperation(WMView * self);
static Bool acceptDropOperation(WMView * self, WMDragOperationType operation);
static WMData *fetchDragData(WMView * self, char *type);

static WMDragSourceProcs _DragSourceProcs = {
	dropDataTypes,
	wantedDropOperation,
	NULL,
	acceptDropOperation,
	NULL,
	NULL,
	fetchDragData
};

static WMArray *requiredDataTypes(WMView * self,
				  WMDragOperationType requestedOperation, WMArray * sourceDataTypes);
static WMDragOperationType allowedOperation(WMView * self,
					    WMDragOperationType requestedOperation, WMArray * sourceDataTypes);
static void performDragOperation(WMView * self, WMArray * dropDatas,
				 WMArray * operationsList, WMPoint * dropLocation);

static WMDragDestinationProcs _DragDestinationProcs = {
	NULL,
	requiredDataTypes,
	allowedOperation,
	NULL,
	performDragOperation,
	NULL
};

#define DEFAULT_WIDTH		60
#define DEFAULT_HEIGHT		30

#define MIN_WIDTH	16
#define MIN_HEIGHT	8

static void colorChangedObserver(void *data, WMNotification * notification)
{
	WMColorPanel *panel = (WMColorPanel *) WMGetNotificationObject(notification);
	WMColorWell *cPtr = (WMColorWell *) data;
	WMColor *color;

	if (!cPtr->flags.active)
		return;

	color = WMGetColorPanelColor(panel);

	WMSetColorWellColor(cPtr, color);
	WMPostNotificationName(WMColorWellDidChangeNotification, cPtr, NULL);
}

static void updateColorCallback(void *self, void *data)
{
	WMColorPanel *panel = (WMColorPanel *) self;
	WMColorWell *cPtr = (ColorWell *) data;
	WMColor *color;

	color = WMGetColorPanelColor(panel);
	WMSetColorWellColor(cPtr, color);
	WMPostNotificationName(WMColorWellDidChangeNotification, cPtr, NULL);
}

static WMArray *getXdndTypeArray(void)
{
	WMArray *types = WMCreateArray(1);
	WMAddToArray(types, XDND_COLOR_DATA_TYPE);
	return types;
}

WMColorWell *WMCreateColorWell(WMWidget * parent)
{
	ColorWell *cPtr;

	cPtr = wmalloc(sizeof(ColorWell));

	cPtr->widgetClass = WC_ColorWell;

	cPtr->view = W_CreateView(W_VIEW(parent));
	if (!cPtr->view) {
		wfree(cPtr);
		return NULL;
	}
	cPtr->view->self = cPtr;

	cPtr->view->delegate = &_ColorWellViewDelegate;

	cPtr->colorView = W_CreateView(cPtr->view);
	if (!cPtr->colorView) {
		W_DestroyView(cPtr->view);
		wfree(cPtr);
		return NULL;
	}
	cPtr->colorView->self = cPtr;

	WMCreateEventHandler(cPtr->view, ExposureMask | StructureNotifyMask
			     | ClientMessageMask, handleEvents, cPtr);

	WMCreateEventHandler(cPtr->colorView, ExposureMask, handleEvents, cPtr);

	WMCreateDragHandler(cPtr->colorView, handleDragEvents, cPtr);

	WMCreateEventHandler(cPtr->view, ButtonPressMask, handleActionEvents, cPtr);
	WMCreateEventHandler(cPtr->colorView, ButtonPressMask, handleActionEvents, cPtr);

	cPtr->colorView->flags.mapWhenRealized = 1;

	cPtr->flags.bordered = 1;

	W_ResizeView(cPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);

	cPtr->color = WMBlackColor(WMWidgetScreen(cPtr));

	WMAddNotificationObserver(colorChangedObserver, cPtr, WMColorPanelColorChangedNotification, NULL);

	WMSetViewDragSourceProcs(cPtr->colorView, &_DragSourceProcs);
	WMSetViewDragDestinationProcs(cPtr->colorView, &_DragDestinationProcs);

	cPtr->xdndTypes = getXdndTypeArray();
	WMRegisterViewForDraggedTypes(cPtr->colorView, cPtr->xdndTypes);

	return cPtr;
}

void WMSetColorWellColor(WMColorWell * cPtr, WMColor * color)
{
	if (cPtr->color && cPtr->color != color) {
		WMReleaseColor(cPtr->color);
		cPtr->color = WMRetainColor(color);
	}

	if (cPtr->colorView->flags.realized && cPtr->colorView->flags.mapped)
		paintColorWell(cPtr);
}

WMColor *WMGetColorWellColor(WMColorWell * cPtr)
{
	return cPtr->color;
}

void WSetColorWellBordered(WMColorWell * cPtr, Bool flag)
{
	flag = ((flag == 0) ? 0 : 1);
	if (cPtr->flags.bordered != flag) {
		cPtr->flags.bordered = flag;
		W_ResizeView(cPtr->view, cPtr->view->size.width, cPtr->view->size.height);
	}
}

static void willResizeColorWell(W_ViewDelegate * self, WMView * view, unsigned int *width, unsigned int *height)
{
	WMColorWell *cPtr = (WMColorWell *) view->self;
	int bw;

	/* Parameter not used, but tell the compiler that it is ok */
	(void) self;

	if (cPtr->flags.bordered) {

		if (*width < MIN_WIDTH)
			*width = MIN_WIDTH;
		if (*height < MIN_HEIGHT)
			*height = MIN_HEIGHT;

		bw = (int)((float)WMIN(*width, *height) * 0.24F);

		W_ResizeView(cPtr->colorView, *width - 2 * bw, *height - 2 * bw);

		if (cPtr->colorView->pos.x != bw || cPtr->colorView->pos.y != bw)
			W_MoveView(cPtr->colorView, bw, bw);
	} else {
		W_ResizeView(cPtr->colorView, *width, *height);

		W_MoveView(cPtr->colorView, 0, 0);
	}
}

static void paintColorWell(ColorWell * cPtr)
{
	W_Screen *scr = cPtr->view->screen;

	W_DrawRelief(scr, cPtr->view->window, 0, 0, cPtr->view->size.width, cPtr->view->size.height, WRRaised);

	W_DrawRelief(scr, cPtr->colorView->window, 0, 0,
		     cPtr->colorView->size.width, cPtr->colorView->size.height, WRSunken);

	if (cPtr->color)
		WMPaintColorSwatch(cPtr->color, cPtr->colorView->window,
				   2, 2, cPtr->colorView->size.width - 4, cPtr->colorView->size.height - 4);
}

static void handleEvents(XEvent * event, void *data)
{
	ColorWell *cPtr = (ColorWell *) data;

	CHECK_CLASS(data, WC_ColorWell);

	switch (event->type) {
	case Expose:
		if (event->xexpose.count != 0)
			break;
		paintColorWell(cPtr);
		break;

	case DestroyNotify:
		destroyColorWell(cPtr);
		break;

	}
}

static WMArray *dropDataTypes(WMView * self)
{
	return ((ColorWell *) self->self)->xdndTypes;
}

static WMDragOperationType wantedDropOperation(WMView * self)
{
	/* Parameter not used, but tell the compiler that it is ok */
	(void) self;

	return WDOperationCopy;
}

static Bool acceptDropOperation(WMView * self, WMDragOperationType operation)
{
	/* Parameter not used, but tell the compiler that it is ok */
	(void) self;

	return (operation == WDOperationCopy);
}

static WMData *fetchDragData(WMView * self, char *type)
{
	char *color = WMGetColorRGBDescription(((WMColorWell *) self->self)->color);
	WMData *data;

	/* Parameter not used, but tell the compiler that it is ok */
	(void) type;

	data = WMCreateDataWithBytes(color, strlen(color) + 1);
	wfree(color);

	return data;
}

static WMPixmap *makeDragPixmap(WMColorWell * cPtr)
{
	WMScreen *scr = cPtr->view->screen;
	Pixmap pix;

	pix = XCreatePixmap(scr->display, W_DRAWABLE(scr), 16, 16, scr->depth);

	XFillRectangle(scr->display, pix, WMColorGC(cPtr->color), 0, 0, 15, 15);

	XDrawRectangle(scr->display, pix, WMColorGC(scr->black), 0, 0, 15, 15);

	return WMCreatePixmapFromXPixmaps(scr, pix, None, 16, 16, scr->depth);
}

static void handleDragEvents(XEvent * event, void *data)
{
	WMColorWell *cPtr = (ColorWell *) data;

	if (event->type == ButtonPress && event->xbutton.button == Button1) {
		/* initialise drag icon */
		WMSetViewDragImage(cPtr->colorView, makeDragPixmap(cPtr));
	}

	WMDragImageFromView(cPtr->colorView, event);
}

static void handleActionEvents(XEvent * event, void *data)
{
	WMColorWell *cPtr = (ColorWell *) data;
	WMScreen *scr = WMWidgetScreen(cPtr);
	WMColorPanel *cpanel;

	/* Parameter not used, but tell the compiler that it is ok */
	(void) event;

	if (cPtr->flags.active)
		W_SetViewBackgroundColor(cPtr->view, scr->gray);
	else
		W_SetViewBackgroundColor(cPtr->view, scr->white);
	paintColorWell(cPtr);

	cPtr->flags.active ^= 1;

	if (cPtr->flags.active) {
		WMPostNotificationName(_ColorWellActivatedNotification, cPtr, NULL);
	}
	cpanel = WMGetColorPanel(scr);

	WMSetColorPanelAction(cpanel, updateColorCallback, cPtr);

	if (cPtr->color)
		WMSetColorPanelColor(cpanel, cPtr->color);
	WMShowColorPanel(cpanel);
}

static void destroyColorWell(ColorWell * cPtr)
{
	WMRemoveNotificationObserver(cPtr);

	if (cPtr->color)
		WMReleaseColor(cPtr->color);

	WMFreeArray(cPtr->xdndTypes);

	wfree(cPtr);
}

static Bool dropIsOk(WMDragOperationType request, WMArray * sourceDataTypes)
{
	WMArrayIterator iter;
	char *type;

	if (request == WDOperationCopy) {
		WM_ITERATE_ARRAY(sourceDataTypes, type, iter) {
			if (type != NULL && strcmp(type, XDND_COLOR_DATA_TYPE) == 0) {
				return True;
			}
		}
	}

	return False;
}

static WMArray *requiredDataTypes(WMView * self, WMDragOperationType request, WMArray * sourceDataTypes)
{
	if (dropIsOk(request, sourceDataTypes))
		return ((ColorWell *) self->self)->xdndTypes;
	else
		return NULL;
}

static WMDragOperationType allowedOperation(WMView * self, WMDragOperationType request, WMArray * sourceDataTypes)
{
	/* Parameter not used, but tell the compiler that it is ok */
	(void) self;

	if (dropIsOk(request, sourceDataTypes))
		return WDOperationCopy;
	else
		return WDOperationNone;
}

static void performDragOperation(WMView * self, WMArray * dropData, WMArray * operations, WMPoint * dropLocation)
{
	char *colorName;
	WMColor *color;
	WMData *data;

	/* Parameter not used, but tell the compiler that it is ok */
	(void) operations;
	(void) dropLocation;

	/* only one operation requested (WDOperationCopy) implies only one data */
	data = (WMData *) WMGetFromArray(dropData, 0);

	if (data != NULL) {
		colorName = (char *)WMDataBytes(data);
		color = WMCreateNamedColor(W_VIEW_SCREEN(self), colorName, True);
		WMSetColorWellColor(self->self, color);
		WMReleaseColor(color);
	}
}
