



#include "WINGsP.h"

typedef struct W_Button {
    W_Class widgetClass;
    WMView *view;

    char *caption;
    
    char *altCaption;
    
    WMFont *font;

    WMColor *textColor;
    WMColor *altTextColor;
    WMColor *disTextColor;

    W_Pixmap *image;
    W_Pixmap *altImage;

    W_Pixmap *dimage;

    void *clientData;
    WMAction *action;

    int tag;
    
    int groupIndex;
    
    float periodicDelay;
    float periodicInterval;
    
    WMHandlerID *timer;		       /* for continuous mode */
    
    struct {
	WMButtonType type:4;
	WMImagePosition imagePosition:4;
	WMAlignment alignment:2;

	unsigned int selected:1;
	
	unsigned int enabled: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 statePush:1;      /* state indicated by relief */

	unsigned int continuous:1;     /* continually perform action */
	/* */
	unsigned int prevSelected:1;

	unsigned int pushed:1;
	
	unsigned int wasPushed:1;

	unsigned int redrawPending:1;
	
	unsigned int addedObserver:1;
    } flags;
} Button;



#define DEFAULT_BUTTON_WIDTH	60
#define DEFAULT_BUTTON_HEIGHT	24
#define DEFAULT_BUTTON_ALIGNMENT	WACenter
#define DEFAULT_BUTTON_IS_BORDERED	True


#define DEFAULT_RADIO_WIDTH	100
#define DEFAULT_RADIO_HEIGHT	20
#define DEFAULT_RADIO_ALIGNMENT	WALeft
#define DEFAULT_RADIO_IMAGE_POSITION	WIPLeft
#define DEFAULT_RADIO_TEXT	"Radio"


#define DEFAULT_SWITCH_WIDTH	100
#define DEFAULT_SWITCH_HEIGHT	20
#define DEFAULT_SWITCH_ALIGNMENT	WALeft
#define DEFAULT_SWITCH_IMAGE_POSITION	WIPLeft
#define DEFAULT_SWITCH_TEXT	"Switch"


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

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


static char *WMPushedRadioNotification="WMPushedRadioNotification";


#define NFONT(b) (b)->view->screen->normalFont


WMButton*
WMCreateCustomButton(WMWidget *parent, int behaviourMask)
{
    Button *bPtr;
    
    bPtr = wmalloc(sizeof(Button));
    memset(bPtr, 0, sizeof(Button));

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

    bPtr->flags.type = 0;

    bPtr->flags.springLoaded = (behaviourMask & WBBSpringLoadedMask)!=0;
    bPtr->flags.pushIn = (behaviourMask & WBBPushInMask)!=0;
    bPtr->flags.pushChange = (behaviourMask & WBBPushChangeMask)!=0;
    bPtr->flags.pushLight = (behaviourMask & WBBPushLightMask)!=0;
    bPtr->flags.stateLight = (behaviourMask & WBBStateLightMask)!=0;
    bPtr->flags.stateChange = (behaviourMask & WBBStateChangeMask)!=0;
    bPtr->flags.statePush = (behaviourMask & WBBStatePushMask)!=0;
    
    W_ResizeView(bPtr->view, DEFAULT_BUTTON_WIDTH, DEFAULT_BUTTON_HEIGHT);
    bPtr->flags.alignment = DEFAULT_BUTTON_ALIGNMENT;
    bPtr->flags.bordered = DEFAULT_BUTTON_IS_BORDERED;
    
    bPtr->flags.enabled = 1;


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

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

    W_ResizeView(bPtr->view, DEFAULT_BUTTON_WIDTH, DEFAULT_BUTTON_HEIGHT);
    bPtr->flags.alignment = DEFAULT_BUTTON_ALIGNMENT;
    bPtr->flags.bordered = DEFAULT_BUTTON_IS_BORDERED;

    return bPtr;
}



WMButton*
WMCreateButton(WMWidget *parent, WMButtonType type)
{
    W_Screen *scrPtr = W_VIEW(parent)->screen;
    Button *bPtr;
    
    switch (type) {
     case WBTMomentaryPush:
	bPtr = WMCreateCustomButton(parent, WBBSpringLoadedMask
				    |WBBPushInMask|WBBPushLightMask);
	break;

     case WBTMomentaryChange:
	bPtr = WMCreateCustomButton(parent, WBBSpringLoadedMask
				    |WBBPushChangeMask);
	break;
		
     case WBTPushOnPushOff:
	bPtr = WMCreateCustomButton(parent, WBBPushInMask|WBBStatePushMask
				    |WBBStateLightMask);
	break;
	
     case WBTToggle:
	bPtr = WMCreateCustomButton(parent, WBBPushInMask|WBBStateChangeMask
				    |WBBStatePushMask);
	break;

     case WBTOnOff:
	bPtr = WMCreateCustomButton(parent, WBBStateLightMask);
	break;

     case WBTSwitch:
	bPtr = WMCreateCustomButton(parent, WBBStateChangeMask);
	bPtr->flags.bordered = 0;
	bPtr->image = WMRetainPixmap(scrPtr->checkButtonImageOff);
	bPtr->altImage = WMRetainPixmap(scrPtr->checkButtonImageOn);
	break;

     case WBTRadio:
	bPtr = WMCreateCustomButton(parent, WBBStateChangeMask);
	bPtr->flags.bordered = 0;
	bPtr->image = WMRetainPixmap(scrPtr->radioButtonImageOff);
	bPtr->altImage = WMRetainPixmap(scrPtr->radioButtonImageOn);
	break;

     default:
     case WBTMomentaryLight:
	bPtr = WMCreateCustomButton(parent, WBBSpringLoadedMask
				    |WBBPushLightMask);
	bPtr->flags.bordered = 1;
	break;
    }
    
    bPtr->flags.type = type;

    if (type==WBTRadio) {
	W_ResizeView(bPtr->view, DEFAULT_RADIO_WIDTH, DEFAULT_RADIO_HEIGHT);
	WMSetButtonText(bPtr, DEFAULT_RADIO_TEXT);
	bPtr->flags.alignment = DEFAULT_RADIO_ALIGNMENT;
	bPtr->flags.imagePosition = DEFAULT_RADIO_IMAGE_POSITION;
    } else if (type==WBTSwitch) {
	W_ResizeView(bPtr->view, DEFAULT_SWITCH_WIDTH, DEFAULT_SWITCH_HEIGHT);
	WMSetButtonText(bPtr, DEFAULT_SWITCH_TEXT);
	bPtr->flags.alignment = DEFAULT_SWITCH_ALIGNMENT;
	bPtr->flags.imagePosition = DEFAULT_SWITCH_IMAGE_POSITION;
    }
    
    return bPtr;
}


static void
updateDisabledMask(WMButton *bPtr)
{
    WMScreen *scr = WMWidgetScreen(bPtr);
    Display *dpy = scr->display;

    if (bPtr->image) {
	XGCValues gcv;

	bPtr->dimage->mask = XCreatePixmap(dpy, scr->stipple,
					   bPtr->dimage->width, 
					   bPtr->dimage->height, 1);

	XSetForeground(dpy, scr->monoGC, 0);
	XFillRectangle(dpy, bPtr->dimage->mask, scr->monoGC, 0, 0,
		       bPtr->dimage->width, bPtr->dimage->height);

	gcv.foreground = 1;
	gcv.background = 0;
	gcv.stipple = scr->stipple;
	gcv.fill_style = FillStippled;
	gcv.clip_mask = bPtr->image->mask;
	gcv.clip_x_origin = 0;
	gcv.clip_y_origin = 0;
	
	XChangeGC(dpy, scr->monoGC, GCForeground|GCBackground|GCStipple
		  |GCFillStyle|GCClipMask|GCClipXOrigin|GCClipYOrigin, &gcv);

	XFillRectangle(dpy, bPtr->dimage->mask, scr->monoGC, 0, 0,
		       bPtr->dimage->width, bPtr->dimage->height);

	gcv.fill_style = FillSolid;
	gcv.clip_mask = None;
	XChangeGC(dpy, scr->monoGC, GCFillStyle|GCClipMask, &gcv);
    }
}

void
WMSetButtonImageDefault(WMButton *bPtr)
{
    WMSetButtonImage (bPtr, WMWidgetScreen(bPtr)->buttonArrow);
    WMSetButtonAltImage (bPtr, WMWidgetScreen(bPtr)->pushedButtonArrow);
}

void
WMSetButtonImage(WMButton *bPtr, WMPixmap *image)
{
    if (bPtr->image!=NULL)
	WMReleasePixmap(bPtr->image);
    bPtr->image = WMRetainPixmap(image);

    if (bPtr->dimage) {
	bPtr->dimage->pixmap = None;
	WMReleasePixmap(bPtr->dimage);
	bPtr->dimage = NULL;
    }

    if (image) {
	bPtr->dimage = WMCreatePixmapFromXPixmaps(WMWidgetScreen(bPtr), 
						  image->pixmap, None,
						  image->width, image->height,
						  image->depth);
	updateDisabledMask(bPtr);
    }

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


void
WMSetButtonAltImage(WMButton *bPtr, WMPixmap *image)
{
    if (bPtr->altImage!=NULL)
	WMReleasePixmap(bPtr->altImage);
    bPtr->altImage = WMRetainPixmap(image);
    
    
    if (bPtr->view->flags.realized) {
	paintButton(bPtr);
    }
}


void
WMSetButtonImagePosition(WMButton *bPtr, WMImagePosition position)
{
    bPtr->flags.imagePosition = position;
    
    
    if (bPtr->view->flags.realized) {
	paintButton(bPtr);
    }
}




void
WMSetButtonTextAlignment(WMButton *bPtr, WMAlignment alignment)
{
    bPtr->flags.alignment = alignment;
    
    
    if (bPtr->view->flags.realized) {
	paintButton(bPtr);
    }
}

void
WMSetButtonText(WMButton *bPtr, char *text)
{
    if (bPtr->caption)
	wfree(bPtr->caption);

    if (text!=NULL) {
	bPtr->caption = wstrdup(text);
    } else {
	bPtr->caption = NULL;
    }
    
    
    if (bPtr->view->flags.realized) {
	paintButton(bPtr);
    }
}


void
WMSetButtonAltText(WMButton *bPtr, char *text)
{
    if (bPtr->altCaption)
	wfree(bPtr->altCaption);
    
    if (text!=NULL) {
	bPtr->altCaption = wstrdup(text);
    } else {
	bPtr->altCaption = NULL;
    }
    
    if (bPtr->view->flags.realized) {
	paintButton(bPtr);
    }
}


void
WMSetButtonTextColor(WMButton *bPtr, WMColor *color)
{
    if (bPtr->textColor)
	WMReleaseColor(bPtr->textColor);

    bPtr->textColor = WMRetainColor(color);
}


void
WMSetButtonAltTextColor(WMButton *bPtr, WMColor *color)
{
    if (bPtr->altTextColor)
	WMReleaseColor(bPtr->altTextColor);

    bPtr->altTextColor = WMRetainColor(color);
}


void
WMSetButtonDisabledTextColor(WMButton *bPtr, WMColor *color)
{
    if (bPtr->disTextColor)
	WMReleaseColor(bPtr->disTextColor);

    bPtr->disTextColor = WMRetainColor(color);
}


void
WMSetButtonSelected(WMButton *bPtr, int isSelected)
{
    bPtr->flags.selected = isSelected;
 
    WMPostNotificationName(WMPushedRadioNotification, bPtr, NULL);
    
    if (bPtr->view->flags.realized) {
	paintButton(bPtr);
    }
}


int
WMGetButtonSelected(WMButton *bPtr)
{
    CHECK_CLASS(bPtr, WC_Button);
    
    return bPtr->flags.selected;
}


void
WMSetButtonBordered(WMButton *bPtr, int isBordered)
{
    bPtr->flags.bordered = isBordered;
    
    if (bPtr->view->flags.realized) {
	paintButton(bPtr);
    }
}


void
WMSetButtonFont(WMButton *bPtr, WMFont *font)
{
    if (bPtr->font)
	WMReleaseFont(bPtr->font);

    bPtr->font = WMRetainFont(font);
}


void
WMSetButtonEnabled(WMButton *bPtr, Bool flag)
{
    bPtr->flags.enabled = flag;

    if (bPtr->view->flags.mapped) {
	paintButton(bPtr);
    }
}


void
WMSetButtonTag(WMButton *bPtr, int tag)
{
    bPtr->tag = tag;
}

    

void
WMPerformButtonClick(WMButton *bPtr)
{
    CHECK_CLASS(bPtr, WC_Button);
  
    if (!bPtr->flags.enabled)
	return;

    bPtr->flags.pushed = 1;
    bPtr->flags.selected = 1;
    
    if (bPtr->view->flags.mapped) {
	paintButton(bPtr);
	XFlush(WMScreenDisplay(WMWidgetScreen(bPtr)));
	wusleep(20000);
    }
  
    if (bPtr->groupIndex>0) {
	WMPostNotificationName(WMPushedRadioNotification, bPtr, NULL);
    }

    if (bPtr->action)
	(*bPtr->action)(bPtr, bPtr->clientData);
     
    bPtr->flags.pushed = 0;
    
    if (bPtr->view->flags.mapped)
	paintButton(bPtr);
}



void
WMSetButtonAction(WMButton *bPtr, WMAction *action, void *clientData)
{
    CHECK_CLASS(bPtr, WC_Button);
    
    bPtr->action = action;
    
    bPtr->clientData = clientData;
}




static void
radioPushObserver(void *observerData, WMNotification *notification)
{
    WMButton *bPtr = (WMButton*)observerData;
    WMButton *pushedButton = (WMButton*)WMGetNotificationObject(notification);

    if (bPtr!=pushedButton && pushedButton->groupIndex == bPtr->groupIndex
	&& bPtr->groupIndex!=0) {
	if (bPtr->flags.selected) {
	    bPtr->flags.selected = 0;
	    paintButton(bPtr);
	}
    }
}



void
WMGroupButtons(WMButton *bPtr, WMButton *newMember)
{
    static int tagIndex = 0;
    
    CHECK_CLASS(bPtr, WC_Button);
    CHECK_CLASS(newMember, WC_Button);

    if (!bPtr->flags.addedObserver) {
	WMAddNotificationObserver(radioPushObserver, bPtr,
				  WMPushedRadioNotification, NULL);
	bPtr->flags.addedObserver = 1;
    }
    if (!newMember->flags.addedObserver) {
	WMAddNotificationObserver(radioPushObserver, newMember,
				  WMPushedRadioNotification, NULL);
	newMember->flags.addedObserver = 1;
    }
    
    if (bPtr->groupIndex==0) {
	bPtr->groupIndex = ++tagIndex;
    }
    newMember->groupIndex = bPtr->groupIndex;
}


void
WMSetButtonContinuous(WMButton *bPtr, Bool flag)
{
    bPtr->flags.continuous = flag;
    if (bPtr->timer) {
	WMDeleteTimerHandler(bPtr->timer);
	bPtr->timer = NULL;
    }
}


void
WMSetButtonPeriodicDelay(WMButton *bPtr, float delay, float interval)
{
    bPtr->periodicInterval = interval;
    bPtr->periodicDelay = delay;
}



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

    gc = NULL;
    caption = bPtr->caption;

    if (bPtr->flags.enabled) {
        textColor = (bPtr->textColor!=NULL
                     ? bPtr->textColor : scrPtr->black);
    } else {
        textColor = (bPtr->disTextColor!=NULL
                     ? bPtr->disTextColor : scrPtr->darkGray);
    }

    if (bPtr->flags.enabled || !bPtr->dimage)
	image = bPtr->image;
    else
	image = bPtr->dimage;
    offset = 0;
    if (bPtr->flags.bordered)
	relief = WRRaised;
    else
	relief = WRFlat;

    if (bPtr->flags.selected) {
        if (bPtr->flags.stateLight) {
            gc = WMColorGC(scrPtr->white);
            textColor = scrPtr->black;
        }

	if (bPtr->flags.stateChange) {
	    if (bPtr->altCaption)
		caption = bPtr->altCaption;
	    if (bPtr->altImage)
		image = bPtr->altImage;
            if (bPtr->altTextColor)
                textColor = bPtr->altTextColor;
        }

	if (bPtr->flags.statePush && bPtr->flags.bordered) {
	    relief = WRSunken;
	    offset = 1;
	}
    }

    if (bPtr->flags.pushed) {
	if (bPtr->flags.pushIn) {
	    relief = WRPushed;
	    offset = 1;
	}
        if (bPtr->flags.pushLight) {
            gc = WMColorGC(scrPtr->white);
            textColor = scrPtr->black;
        }

	if (bPtr->flags.pushChange) {
	    if (bPtr->altCaption)
		caption = bPtr->altCaption;
	    if (bPtr->altImage)
		image = bPtr->altImage;
            if (bPtr->altTextColor)
                textColor = bPtr->altTextColor;
	}
    }

    W_PaintTextAndImage(bPtr->view, True, WMColorGC(textColor),
			(bPtr->font!=NULL ? bPtr->font : scrPtr->normalFont),
			relief, caption, bPtr->flags.alignment, image, 
			bPtr->flags.imagePosition, gc, offset);
}



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;
    }
}


static void 
autoRepeat(void *data)
{
    Button *bPtr = (Button*)data;

    if (bPtr->action && bPtr->flags.pushed)
	(*bPtr->action)(bPtr, bPtr->clientData);

    bPtr->timer = WMAddTimerHandler((int)(bPtr->periodicInterval*1000),
				    autoRepeat, bPtr);
}


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

    CHECK_CLASS(data, WC_Button);

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

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

     case ButtonPress:
	if (event->xbutton.button == Button1) {
	    if (bPtr->groupIndex>0) {
		if (!bPtr->flags.selected)
		    doclick = 1;
		bPtr->flags.pushed = 1;
		bPtr->flags.selected = 1;
		dopaint = 1;
		break;
	    }
	    bPtr->flags.wasPushed = 0;
	    bPtr->flags.pushed = 1;
	    bPtr->flags.prevSelected = bPtr->flags.selected;
	    bPtr->flags.selected = !bPtr->flags.selected;
	    dopaint = 1;
	    
	    if (bPtr->flags.continuous && !bPtr->timer) {
		bPtr->timer = WMAddTimerHandler((int)(bPtr->periodicDelay*1000),
						autoRepeat, bPtr);
	    }
	}
	break;
 
     case ButtonRelease:
	if (event->xbutton.button == Button1) {
	    if (bPtr->flags.pushed) {
		if (bPtr->groupIndex==0)
		    doclick = 1;
		dopaint = 1;
		if (bPtr->flags.springLoaded) {
		    bPtr->flags.selected = bPtr->flags.prevSelected;
		}
	    }
	    bPtr->flags.pushed = 0;
	}
	if (bPtr->timer) {
	    WMDeleteTimerHandler(bPtr->timer);
	    bPtr->timer = NULL;
	}
	break;	
    }

    if (dopaint)
	paintButton(bPtr);
    
    if (doclick) {
	if (bPtr->flags.selected && bPtr->groupIndex>0) {
	    WMPostNotificationName(WMPushedRadioNotification, bPtr, NULL);
	}
	
	if (bPtr->action)
	    (*bPtr->action)(bPtr, bPtr->clientData);
    }
}



static void
destroyButton(Button *bPtr)
{
    if (bPtr->flags.addedObserver) {
	WMRemoveNotificationObserver(bPtr);
    }

    if (bPtr->timer)
	WMDeleteTimerHandler(bPtr->timer);
    
    if (bPtr->font)
	WMReleaseFont(bPtr->font);
    
    if (bPtr->caption)
	wfree(bPtr->caption);

    if (bPtr->altCaption)
	wfree(bPtr->altCaption);

    if (bPtr->textColor)
        WMReleaseColor(bPtr->textColor);

    if (bPtr->altTextColor)
        WMReleaseColor(bPtr->altTextColor);

    if (bPtr->disTextColor)
        WMReleaseColor(bPtr->disTextColor);

    if (bPtr->image)
	WMReleasePixmap(bPtr->image);

    if (bPtr->dimage) {
	/* yuck.. kluge */
	bPtr->dimage->pixmap = None;

	WMReleasePixmap(bPtr->dimage);
    }
    if (bPtr->altImage)
	WMReleasePixmap(bPtr->altImage);

    wfree(bPtr);
}


