



#include "WINGsP.h"

typedef struct W_Button {
    W_Class widgetClass;
    W_View *view;

    char *caption;
    int captionHeight;
    
    char *altCaption;
    int altCaptionHeight;
    
    W_Pixmap *image;
    W_Pixmap *altImage;

    void *onClickData;
    WMSimpleCallback *onClick;

    WMButtonType type;
    WMImagePosition imagePosition;
    WMAlignment alignment;
    
    int tag;
    
    struct {
	unsigned int selected:1;
	
	unsigned int disabled:1;

	unsigned int bordered:1;

	unsigned int springLoaded:1;
	
	unsigned int pushIn:1;	       /* change relief while pushed */
	
	unsigned int pushLight:1;      /* highlight while pushed */
	
	unsigned int pushChange:1;     /* change caption while pushed */
	    
	unsigned int stateLight:1;     /* state indicated by highlight */
	
	unsigned int stateChange:1;    /* state indicated by caption change */
	
	/* */
	unsigned int prevSelected:1;
	
	unsigned int pushed:1;
	
	unsigned int configured:1;
	
	unsigned int redrawPending:1;
    } flags;
} Button;



static WMArgument buttonArguments[] = {
    {WARG_WIDTH,	(WMValue)60},
    {WARG_HEIGHT,	(WMValue)24},
    {WARG_ALIGNMENT,	(WMValue)WACenter},
    {WARG_BORDERED,	(WMValue)1},
    {WARG_CAPTION,	(WMValue)"Button"},
};


static WMArgument radioArguments[] = {
    {WARG_WIDTH,	(WMValue)100},
    {WARG_HEIGHT,	(WMValue)20},
    {WARG_ALIGNMENT,	(WMValue)WALeft},
    {WARG_IMAGE_POSITION,(WMValue)WIPLeft},
    {WARG_CAPTION,	(WMValue)"Radio"},
};


static WMArgument switchArguments[] = {
    {WARG_WIDTH,	(WMValue)100},
    {WARG_HEIGHT,	(WMValue)20},
    {WARG_ALIGNMENT,	(WMValue)WARight},
    {WARG_IMAGE_POSITION,(WMValue)WIPRight},
    {WARG_CAPTION,	(WMValue)"Switch"},
};




static void destroyButton(Button *but);
static void paintButton(Button *bPtr);


static int configureButtonArg(Button *but, WMArgument *arg);
static void handleEvents(XEvent *event, void *data);
static void handleButtonActionEvents(XEvent *event, void *data);


	      
	      
WMButton*
WMCreateButton(WMWidget *parent, WMButtonType type, WMArgument *argv, int argc)
{
    Button *bPtr;
    W_Screen *scrPtr = W_VIEW(parent)->screen;
    
    bPtr = wmalloc(sizeof(Button));
    memset(bPtr, 0, sizeof(Button));

    bPtr->widgetClass = WC_Button;
    
    bPtr->view = W_CreateView(W_VIEW(parent));
    if (!bPtr->view) {
	free(bPtr);
	return NULL;
    }
    bPtr->view->attribFlags |= CWBackPixel;
    bPtr->view->attribs.background_pixel = W_VIEW(parent)->screen->lightPixel;
    
    bPtr->type = type;
    
    switch (type) {
     case WBTMomentaryPush:
	bPtr->flags.springLoaded = 1;
	bPtr->flags.pushIn = 1;
	bPtr->flags.bordered = 1;
	break;

     case WBTMomentaryChange:
	bPtr->flags.springLoaded = 1;
	bPtr->flags.pushChange = 1;
	bPtr->flags.bordered = 1;
	break;
	
     case WBTMomentaryLight:
	bPtr->flags.springLoaded = 1;
	bPtr->flags.pushLight = 1;
	bPtr->flags.bordered = 1;
	break;
	
     case WBTPushOnPushOff:
	bPtr->flags.pushIn = 1;
	bPtr->flags.stateLight = 1;
	break;
	
     case WBTToggle:
	bPtr->flags.pushIn = 1;
	bPtr->flags.stateChange = 1;
	break;

     case WBTOnOff:
	bPtr->flags.stateLight = 1;
	break;

     case WBTSwitch:
	bPtr->flags.stateChange = 1;
	bPtr->image = WMRetainPixmap(scrPtr->checkButtonImageOff);
	bPtr->altImage = WMRetainPixmap(scrPtr->checkButtonImageOn);
	break;

     case WBTRadio:
	bPtr->flags.stateChange = 1;
	bPtr->image = WMRetainPixmap(scrPtr->radioButtonImageOff);
	bPtr->altImage = WMRetainPixmap(scrPtr->radioButtonImageOn);
	break;
    }

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

    if (!WMConfigureButton(bPtr, argv, argc)) {
	W_DestroyView(bPtr->view);
	return NULL;
    }

    WMCreateEventHandler(bPtr->view, ButtonPressMask|ButtonReleaseMask
			 |EnterWindowMask|LeaveWindowMask, 
			 handleButtonActionEvents, bPtr);

    return bPtr;
}


#if 0
WMButton*
W_CloneButton(WMWidget *widget)
{
    Button *bPtr = (Button*)widget;
    Button *newPtr;
    W_Screen *scrPtr = W_VIEW(widget)->screen;

    CHECK_CLASS(widget, WC_Button);
    
    
    newPtr = wmalloc(sizeof(Button));
    memset(newPtr, 0, sizeof(Button));

    newPtr->widgetClass = WC_Button;
    
    newPtr->view = W_CreateView(bPtr->view);
    if (!newPtr->view) {
	free(newPtr);
	return NULL;
    }
    newPtr->view->attribFlags |= CWBackPixel;
    newPtr->view->attribs.background_pixel = bPtr->view->attribs.background_pixel;
    
    newPtr->type = bPtr->type;
    
    if (bPtr->caption)
	newPtr->caption = strdup(bPtr->caption);
    
    if (bPtr->altCaption)
	newPtr->altCaption = strdup(bPtr->altCaption);

    if (bPtr->image)
	newPtr->image = WMRetainPixmap(newPtr->image);
    
    if (bPtr->altImage)
	newPtr->altImage = WMRetainPixmap(newPtr->altImage);

    newPtr->onClickData = bPtr->onClickData;
    newPtr->onClick = bPtr->onClick;

    newPtr->imagePosition = bPtr->imagePosition;

    newPtr->alignment = bPtr->alignment;

    newPtr->flags = bPtr->flags;
    
    WMCreateEventHandler(newPtr->view, ExposureMask|StructureNotifyMask
			 |ClientMessageMask, handleEvents, newPtr);

    WMCreateEventHandler(newPtr->view, ButtonPressMask|ButtonReleaseMask
			 |EnterWindowMask|LeaveWindowMask, 
			 handleButtonActionEvents, newPtr);

    return newPtr;
}
#endif

void
WMPerformButtonClick(WMButton *bPtr)
{
    XEvent event;
    
    CHECK_CLASS(bPtr, WC_Button);
    
    event.type = ButtonPress;
    handleButtonActionEvents(&event, bPtr);
    
    
    
    event.type = ButtonRelease;
    handleButtonActionEvents(&event, bPtr);
}



void 
W_BindButtonEvent(WMButton *bPtr, WMEventType eventType, WMCallBack *callback,
		  void *clientData)
{
    CHECK_CLASS(bPtr, WC_Button);
    
    switch (eventType) {
     case WEV_CLICK:
	bPtr->onClick = callback;
	bPtr->onClickData = clientData;
	break;
     default:
	wWarning("invalid event type %i for button\n");
	break;
    }
}


static int
configureButtonArg(Button *but, WMArgument *arg)
{
    switch (arg->argument) {
     case WARG_IMAGE:
	if (but->image!=NULL)
	    WMReleasePixmap(but->image);
	but->image = WMRetainPixmap((WMPixmap*)arg->value);
	break;
	
     case WARG_ALT_IMAGE:
	if (but->altImage!=NULL)
	    WMReleasePixmap(but->altImage);
	but->altImage = WMRetainPixmap((WMPixmap*)arg->value);
	break;
	
     case WARG_IMAGE_POSITION:
	but->imagePosition = (WMImagePosition)arg->value;
	break;
	
     case WARG_ALIGNMENT:
	but->alignment = (WMAlignment)arg->value;
	break;
	
     case WARG_CAPTION:
	if (but->caption)
	    free(but->caption);
	if (arg->value)
	    but->caption = strdup((char*)arg->value);
	else
	    but->caption = NULL;
	break;

     case WARG_ALT_CAPTION:
	if (but->altCaption)
	    free(but->altCaption);
	if (arg->value)
	    but->altCaption = strdup((char*)arg->value);
	else
	    but->altCaption = NULL;
	break;
	
     case WARG_SELECTED:
	but->flags.selected = ((long)arg->value!=0);
	break;
	
     case WARG_BORDERED:
	but->flags.bordered = ((long)arg->value!=0);
	break;

     case WARG_DISABLED:
	but->flags.disabled = ((long)arg->value!=0);
	break;
	
     case WARG_TAG:
	but->tag = (long)arg->value;
	break;
	

     default:
	if (!W_ConfigureViewArg(but->view, arg)) {
	    wWarning("bad argument value %i in configureButton",
		     arg->argument);
	    return False;
	}
    }
    return True;
}


int
WMConfigureButton(WMButton *bPtr, WMArgument *argv, int argc)
{
    int i;
    
    CHECK_CLASS(bPtr, WC_Button);

    if (!bPtr->flags.configured) {
	switch (bPtr->type) {
	 case WBTRadio:
	    for (i=0; i<sizeof(radioArguments)/sizeof(WMArgument); i++) {
		if (!configureButtonArg(bPtr, &(radioArguments[i])))
		    return False;
	    }
	    break;

	 case WBTSwitch:
	    for (i=0; i<sizeof(switchArguments)/sizeof(WMArgument); i++) {
		if (!configureButtonArg(bPtr, &(switchArguments[i])))
		    return False;
	    }
	    break;

	 default:
	    for (i=0; i<sizeof(buttonArguments)/sizeof(WMArgument); i++) {
		if (!configureButtonArg(bPtr, &(buttonArguments[i])))
		    return False;
	    }
	}
	bPtr->flags.configured = 1;
    }

    for (i=0; i<argc; i++) {
	if (!configureButtonArg(bPtr, &(argv[i])))
	    return False;
    }
    
    if (bPtr->caption!=NULL) {
	bPtr->captionHeight = 
	    W_GetTextHeight(bPtr->view->screen->normalFont, bPtr->caption, 
			    bPtr->view->size.width, True);
    } else {
	bPtr->captionHeight = 0;
    }
    if (bPtr->altCaption!=NULL) {
	bPtr->altCaptionHeight = 
	    W_GetTextHeight(bPtr->view->screen->normalFont, bPtr->altCaption,
			    bPtr->view->size.width, True);
    } else {
	bPtr->altCaptionHeight = 0;
    }


    if (bPtr->view->flags.realized) {
	paintButton(bPtr);
    }
        
    return True;
}




static void
paintButton(Button *bPtr)
{
    W_Screen *scrPtr = bPtr->view->screen;
    GC gc;
    WMReliefType relief;
    int offset;
    char *caption;
    int captionHeight;
    WMPixmap *image;

    gc = NULL;
    caption = bPtr->caption;
    captionHeight = bPtr->captionHeight;
    image = bPtr->image;
    offset = 0;
    if (bPtr->flags.bordered)
	relief = WRRaised;
    else
	relief = WRFlat;

    if (bPtr->flags.selected) {
	if (bPtr->flags.stateLight)
	    gc = scrPtr->whiteGC;

	if (bPtr->flags.stateChange) {
	    if (bPtr->altCaption) {
		caption = bPtr->altCaption;
		captionHeight = bPtr->altCaptionHeight;
	    }
	    if (bPtr->altImage)
		image = bPtr->altImage;
	}
    }
    
    if (bPtr->flags.pushed) {
	if (bPtr->flags.pushIn && bPtr->flags.selected) {
	    relief = WRSunken;
	    offset = 1;
	}

	if (bPtr->flags.pushLight)
	    gc = scrPtr->whiteGC;
	
	if (bPtr->flags.pushChange) {
	    if (bPtr->altCaption) {
		caption = bPtr->altCaption;
		captionHeight = bPtr->altCaptionHeight;
	    }
	    if (bPtr->altImage)
		image = bPtr->altImage;
	}
    }
    

    if (bPtr->flags.disabled)
	XSetForeground(scrPtr->display, scrPtr->normalFontGC,
		       scrPtr->darkPixel);

    W_PaintTextAndImage(bPtr->view, False, scrPtr->normalFontGC,
			scrPtr->normalFont, relief, caption, captionHeight,
			bPtr->alignment, image, bPtr->imagePosition, gc, offset);
    
    if (bPtr->flags.disabled)
	XSetForeground(scrPtr->display, scrPtr->normalFontGC,
		       scrPtr->blackPixel);
}



static void
handleEvents(XEvent *event, void *data)
{
    Button *bPtr = (Button*)data;

    CHECK_CLASS(data, WC_Button);


    switch (event->type) {
     case Expose:
	if (event->xexpose.count!=0)
	    break;
	paintButton(bPtr);
	break;
	
     case DestroyNotify:
	destroyButton(bPtr);
	break;
	
     case ClientMessage:
	if (event->xclient.message_type==bPtr->view->screen->internalMessage) {
	    if (event->xclient.data.l[1]==WM_RADIO_PRESS
		&& event->xclient.data.l[2]==bPtr->tag) {
		/* some other button was pushed down */
		if (bPtr->flags.selected 
		    && event->xclient.data.l[0]!=bPtr->view->window) {
		    bPtr->flags.selected = 0;
		    paintButton(bPtr);
		}
	    }
	}
	break;
    }
}


static void
handleButtonActionEvents(XEvent *event, void *data)
{
    Button *bPtr = (Button*)data;
    int doclick = 0, dopaint=0;

    CHECK_CLASS(data, WC_Button);

    if (bPtr->flags.disabled)
	return;
    
    switch (event->type) {
     case EnterNotify:
	if (bPtr->flags.pushed) {
	    bPtr->flags.selected = !bPtr->flags.prevSelected;
	    dopaint = 1;
	}
	break;

     case LeaveNotify:
	if (bPtr->flags.pushed) {
	    bPtr->flags.selected = bPtr->flags.prevSelected;
	    dopaint = 1;
	}	
	break;

     case ButtonPress:
	if (bPtr->tag>0 && bPtr->flags.selected)
	    break;
	bPtr->flags.pushed = 1;
	bPtr->flags.prevSelected = bPtr->flags.selected;
	bPtr->flags.selected = !bPtr->flags.selected;
	dopaint = 1;
	break;
 
     case ButtonRelease:
	if (bPtr->flags.pushed) {
	    doclick = 1;
	    dopaint = 1;
	    if (bPtr->flags.springLoaded) {
		bPtr->flags.selected = bPtr->flags.prevSelected;
	    }
	}
	bPtr->flags.pushed = 0;
	break;	
    }

    if (dopaint)
	paintButton(bPtr);
    
    if (doclick) {
	XEvent ev;
	
	if (bPtr->onClick)
	    (*bPtr->onClick)(bPtr, bPtr->onClickData);

	if (bPtr->flags.selected && bPtr->tag>0) {
	    SETUP_INTERNAL_MESSAGE(ev, bPtr->view->screen);
	    ev.xclient.format=32;
	    ev.xclient.data.l[0]=bPtr->view->window;
	    ev.xclient.data.l[1]=WM_RADIO_PRESS;
	    ev.xclient.data.l[2]=bPtr->tag;

	    W_BroadcastMessage(bPtr->view->parent, &ev);
	}
    }
}



static void
destroyButton(Button *but)
{
    CHECK_CLASS(but, WC_Button);
   
    if (but->caption)
	free(but->caption);

    if (but->altCaption)
	free(but->altCaption);
    
    if (but->image)
	WMReleasePixmap(but->image);
    
    if (but->altImage)
	WMReleasePixmap(but->altImage);

    free(but);
}
