



#include "WINGsP.h"


typedef struct W_PopUpButton {
    W_Class widgetClass;
    WMView *view;
    
    void *clientData;
    WMAction *action;

    char *caption;

    WMBag *items;

    short selectedItemIndex;
    
    short highlightedItem;
    
    WMView *menuView;		       /* override redirect popup menu */

    WMHandlerID timer;		       /* for autoscroll */

    /**/
    int scrollStartY;		       /* for autoscroll */
    
    struct {
	unsigned int pullsDown:1;

	unsigned int configured:1;
	
	unsigned int insideMenu:1;
	
	unsigned int enabled:1;

    } flags;
} PopUpButton;


#define MENU_BLINK_DELAY	60000
#define MENU_BLINK_COUNT	2

#define SCROLL_DELAY		10


#define DEFAULT_WIDTH	60
#define DEFAULT_HEIGHT 	20
#define DEFAULT_CAPTION	""


static void destroyPopUpButton(PopUpButton *bPtr);
static void paintPopUpButton(PopUpButton *bPtr);

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

static void resizeMenu(PopUpButton *bPtr);

	      
WMPopUpButton*
WMCreatePopUpButton(WMWidget *parent)
{
    PopUpButton *bPtr;
    W_Screen *scr = W_VIEW(parent)->screen;

    
    bPtr = wmalloc(sizeof(PopUpButton));
    memset(bPtr, 0, sizeof(PopUpButton));

    bPtr->widgetClass = WC_PopUpButton;
    
    bPtr->view = W_CreateView(W_VIEW(parent));
    if (!bPtr->view) {
	wfree(bPtr);
	return NULL;
    }
    bPtr->view->self = bPtr;
    
    WMCreateEventHandler(bPtr->view, ExposureMask|StructureNotifyMask
			 |ClientMessageMask, handleEvents, bPtr);


    W_ResizeView(bPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
    bPtr->caption = wstrdup(DEFAULT_CAPTION);

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

    bPtr->flags.enabled = 1;
    
    bPtr->items = WMCreateBag(4);

    bPtr->selectedItemIndex = -1;

    bPtr->menuView = W_CreateTopView(scr);
    bPtr->menuView->attribs.override_redirect = True;
    bPtr->menuView->attribFlags |= CWOverrideRedirect;

    W_ResizeView(bPtr->menuView, bPtr->view->size.width, 1);

    WMCreateEventHandler(bPtr->menuView, ButtonPressMask|ButtonReleaseMask
			 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask
			 |ExposureMask, handleActionEvents, bPtr);

    return bPtr;
}


void
WMSetPopUpButtonAction(WMPopUpButton *bPtr, WMAction *action, void *clientData)
{
    CHECK_CLASS(bPtr, WC_PopUpButton);
    
    bPtr->action = action;
    
    bPtr->clientData = clientData;
}


WMMenuItem*
WMAddPopUpButtonItem(WMPopUpButton *bPtr, char *title)
{
    WMMenuItem *item;
    
    CHECK_CLASS(bPtr, WC_PopUpButton);

    item = WMCreateMenuItem();
    WMSetMenuItemTitle(item, title);

    WMPutInBag(bPtr->items, item);

    if (bPtr->menuView && bPtr->menuView->flags.realized)
	resizeMenu(bPtr);

    return item;
}


WMMenuItem*
WMInsertPopUpButtonItem(WMPopUpButton *bPtr, int index, char *title)
{
    WMMenuItem *item;

    CHECK_CLASS(bPtr, WC_PopUpButton);
    
    item = WMCreateMenuItem();
    WMSetMenuItemTitle(item, title);

    WMInsertInBag(bPtr->items, index, item);
  
    /* if there is an selected item, update it's index to match the new 
     * position */
    if (index < bPtr->selectedItemIndex)
	bPtr->selectedItemIndex++;
    
    if (bPtr->menuView && bPtr->menuView->flags.realized)
	resizeMenu(bPtr);
    
    return item;
}


void
WMRemovePopUpButtonItem(WMPopUpButton *bPtr, int index)
{
    WMMenuItem *item;

    CHECK_CLASS(bPtr, WC_PopUpButton);

    wassertr(index >= 0 && index < WMGetBagItemCount(bPtr->items));
    

    item = WMGetFromBag(bPtr->items, index);
    WMDeleteFromBag(bPtr->items, index);

    WMDestroyMenuItem(item);

    if (bPtr->selectedItemIndex >= 0 && !bPtr->flags.pullsDown) {
	if (index < bPtr->selectedItemIndex)
	    bPtr->selectedItemIndex--;
	else if (index == bPtr->selectedItemIndex) {
	    /* reselect first item if the removed item is the
	     * selected one */
	    bPtr->selectedItemIndex = 0;
	    if (bPtr->view->flags.mapped)
		paintPopUpButton(bPtr);
	}
    }
    
    if (bPtr->menuView && bPtr->menuView->flags.realized)
	resizeMenu(bPtr);
}


void
WMSetPopUpButtonEnabled(WMPopUpButton *bPtr, Bool flag)
{
    bPtr->flags.enabled = flag;
    if (bPtr->view->flags.mapped)
	paintPopUpButton(bPtr);
}


Bool
WMGetPopUpButtonEnabled(WMPopUpButton *bPtr)
{
    return bPtr->flags.enabled;
}


void
WMSetPopUpButtonSelectedItem(WMPopUpButton *bPtr, int index)
{

    wassertr(index < WMGetBagItemCount(bPtr->items));

    /* if (index >= WMGetBagCount(bPtr->items))
        index = -1;*/

    bPtr->selectedItemIndex = index;
    
    if (bPtr->view->flags.mapped)
	paintPopUpButton(bPtr);
}

int
WMGetPopUpButtonSelectedItem(WMPopUpButton *bPtr)
{
    if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
	return -1;
    else
	return bPtr->selectedItemIndex;
}


void
WMSetPopUpButtonText(WMPopUpButton *bPtr, char *text)
{
    if (bPtr->caption)
	wfree(bPtr->caption);
    if (text)
	bPtr->caption = wstrdup(text);
    else
	bPtr->caption = NULL;
    if (bPtr->view->flags.realized) {
	if (bPtr->flags.pullsDown || bPtr->selectedItemIndex < 0) {
	    paintPopUpButton(bPtr);
	}
    }
}



void 
WMSetPopUpButtonItemEnabled(WMPopUpButton *bPtr, int index, Bool flag)
{
    WMMenuItem *item;

    item = WMGetFromBag(bPtr->items, index);
    wassertr(item != NULL);
    
    WMSetMenuItemEnabled(item, flag);
}


Bool
WMGetPopUpButtonItemEnabled(WMPopUpButton *bPtr, int index)
{
    WMMenuItem *item;

    item = WMGetFromBag(bPtr->items, index);
    wassertrv(item != NULL, False);

    return WMGetMenuItemEnabled(item);
}


void
WMSetPopUpButtonPullsDown(WMPopUpButton *bPtr, Bool flag)
{
    bPtr->flags.pullsDown = flag;
    if (flag) {
	bPtr->selectedItemIndex = -1;
    }

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


int
WMGetPopUpButtonNumberOfItems(WMPopUpButton *bPtr)
{
    return WMGetBagItemCount(bPtr->items);
}


char*
WMGetPopUpButtonItem(WMPopUpButton *bPtr, int index)
{
    WMMenuItem *item;

    if (index >= WMGetBagItemCount(bPtr->items) || index < 0)
	return NULL;
    
    item = WMGetFromBag(bPtr->items, index);
    if (item == NULL)
	return NULL;

    return WMGetMenuItemTitle(item);
}


WMMenuItem*
WMGetPopUpButtonMenuItem(WMPopUpButton *bPtr, int index)
{
    WMMenuItem *item;

    item = WMGetFromBag(bPtr->items, index);

    return item;
}



static void
paintPopUpButton(PopUpButton *bPtr)
{
    W_Screen *scr = bPtr->view->screen;
    char *caption;
    Pixmap pixmap;

    
    if (bPtr->flags.pullsDown) {
	caption = bPtr->caption;
    } else {
	if (bPtr->selectedItemIndex < 0) {
	    /* if no item selected, show the caption */
	    caption = bPtr->caption;
	} else {
	    caption = WMGetPopUpButtonItem(bPtr, bPtr->selectedItemIndex);
	}
    }

    pixmap = XCreatePixmap(scr->display, bPtr->view->window, 
			   bPtr->view->size.width, bPtr->view->size.height,
			   scr->depth);
    XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0,
		   bPtr->view->size.width, bPtr->view->size.height);

    W_DrawRelief(scr, pixmap, 0, 0, bPtr->view->size.width,
		 bPtr->view->size.height, WRRaised);

    if (caption) {
	W_PaintText(bPtr->view, pixmap, scr->normalFont, 6, 
		    (bPtr->view->size.height-WMFontHeight(scr->normalFont))/2,
		    bPtr->view->size.width, WALeft, 
		    bPtr->flags.enabled ? WMColorGC(scr->black) : WMColorGC(scr->darkGray),
		    False, caption, strlen(caption));
    }

    if (bPtr->flags.pullsDown) {
	XCopyArea(scr->display, scr->pullDownIndicator->pixmap, 
		  pixmap, scr->copyGC, 0, 0, scr->pullDownIndicator->width,
		  scr->pullDownIndicator->height, 
		  bPtr->view->size.width-scr->pullDownIndicator->width-4,
		  (bPtr->view->size.height-scr->pullDownIndicator->height)/2);
    } else {
	int x, y;
	
	x = bPtr->view->size.width - scr->popUpIndicator->width - 4;
	y = (bPtr->view->size.height-scr->popUpIndicator->height)/2;
	
	XSetClipOrigin(scr->display, scr->clipGC, x, y);
	XSetClipMask(scr->display, scr->clipGC, scr->popUpIndicator->mask);
	XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap,
		  scr->clipGC, 0, 0, scr->popUpIndicator->width,
		  scr->popUpIndicator->height, x, y);
    }

    XCopyArea(scr->display, pixmap, bPtr->view->window, scr->copyGC, 0, 0,
	      bPtr->view->size.width, bPtr->view->size.height, 0, 0);
    
    XFreePixmap(scr->display, pixmap);
}



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

    CHECK_CLASS(data, WC_PopUpButton);


    switch (event->type) {
     case Expose:
	if (event->xexpose.count!=0)
	    break;
	paintPopUpButton(bPtr);
	break;
	
     case DestroyNotify:
	destroyPopUpButton(bPtr);
	break;
    }
}



static void
paintMenuEntry(PopUpButton *bPtr, int index, int highlight)
{
    W_Screen *scr = bPtr->view->screen;
    int yo;
    int width, height, itemHeight, itemCount;
    char *title;

    itemCount = WMGetBagItemCount(bPtr->items);
    if (index < 0 || index >= itemCount)
        return;

    itemHeight = bPtr->view->size.height;
    width = bPtr->view->size.width;
    height = itemHeight * itemCount;
    yo = (itemHeight - WMFontHeight(scr->normalFont))/2;

    if (!highlight) {
	XClearArea(scr->display, bPtr->menuView->window, 0, index*itemHeight,
		   width, itemHeight, False);
	return;
    } else if (index < 0 && bPtr->flags.pullsDown) {
	return;
    }
	
    XFillRectangle(scr->display, bPtr->menuView->window, WMColorGC(scr->white),
		   1, index*itemHeight+1, width-3, itemHeight-3);
    
    title = WMGetPopUpButtonItem(bPtr, index);

    W_DrawRelief(scr, bPtr->menuView->window, 0, index*itemHeight, 
		 width, itemHeight, WRRaised);

    W_PaintText(bPtr->menuView, bPtr->menuView->window, scr->normalFont,  6, 
		index*itemHeight + yo, width, WALeft, WMColorGC(scr->black), 
		False, title, strlen(title));

    if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) {
	XCopyArea(scr->display, scr->popUpIndicator->pixmap, 
		  bPtr->menuView->window, scr->copyGC, 0, 0, 
		  scr->popUpIndicator->width, scr->popUpIndicator->height,
		  width-scr->popUpIndicator->width-4, 
		  index*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
    }
}


Pixmap
makeMenuPixmap(PopUpButton *bPtr)
{
    Pixmap pixmap;
    W_Screen *scr = bPtr->view->screen;
    WMMenuItem *item;
    WMBagIterator iter;
    int yo, i;
    int width, height, itemHeight;
    
    itemHeight = bPtr->view->size.height;
    width = bPtr->view->size.width;
    height = itemHeight * WMGetBagItemCount(bPtr->items);
    yo = (itemHeight - WMFontHeight(scr->normalFont))/2;
    
    pixmap = XCreatePixmap(scr->display, bPtr->view->window, width, height, 
			   scr->depth);
    
    XFillRectangle(scr->display, pixmap, WMColorGC(scr->gray), 0, 0,
		   width, height);

    i = 0;
    WM_ITERATE_BAG(bPtr->items, item, iter) {
	GC gc;
	char *text;

	text = WMGetMenuItemTitle(item);

	W_DrawRelief(scr, pixmap, 0, i*itemHeight, width, itemHeight, 
		     WRRaised);

        if (!WMGetMenuItemEnabled(item))
	    gc = WMColorGC(scr->darkGray);
	else
	    gc = WMColorGC(scr->black);

	W_PaintText(bPtr->menuView, pixmap, scr->normalFont,  6, 
		    i*itemHeight + yo, width, WALeft, gc, False,
		    text, strlen(text));
	
	if (!bPtr->flags.pullsDown && i == bPtr->selectedItemIndex) {
	    XCopyArea(scr->display, scr->popUpIndicator->pixmap, pixmap, 
		      scr->copyGC, 0, 0, scr->popUpIndicator->width,
		      scr->popUpIndicator->height, 
		      width-scr->popUpIndicator->width-4,
		      i*itemHeight+(itemHeight-scr->popUpIndicator->height)/2);
	}
	
	i++;
    }
    
    return pixmap;
}


static void
resizeMenu(PopUpButton *bPtr)
{
    int height;
    
    height = WMGetBagItemCount(bPtr->items) * bPtr->view->size.height;
    if (height > 0)
	W_ResizeView(bPtr->menuView, bPtr->view->size.width, height);
}


static void
popUpMenu(PopUpButton *bPtr)
{
    W_Screen *scr = bPtr->view->screen;
    Window dummyW;
    int x, y;

    if (!bPtr->menuView->flags.realized) {
	W_RealizeView(bPtr->menuView);
	resizeMenu(bPtr);
    }
    
    if (WMGetBagItemCount(bPtr->items) < 1)
	return;
    
    XTranslateCoordinates(scr->display, bPtr->view->window, scr->rootWin,
			  0, 0, &x, &y, &dummyW);

    if (bPtr->flags.pullsDown) {
	y += bPtr->view->size.height;
    } else {
	y -= bPtr->view->size.height*bPtr->selectedItemIndex;
    }
    W_MoveView(bPtr->menuView, x, y);
    
    XSetWindowBackgroundPixmap(scr->display, bPtr->menuView->window,
			       makeMenuPixmap(bPtr));
    XClearWindow(scr->display, bPtr->menuView->window);
    
    W_MapView(bPtr->menuView);
    
    bPtr->highlightedItem = 0;
    if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
	paintMenuEntry(bPtr, bPtr->selectedItemIndex, True);
}


static void
popDownMenu(PopUpButton *bPtr)
{
    W_UnmapView(bPtr->menuView);
    
    /* free the background pixmap used to draw the menu contents */
    XSetWindowBackgroundPixmap(bPtr->view->screen->display, 
			       bPtr->menuView->window, None);
}


static void
autoScroll(void *data)
{
    PopUpButton *bPtr = (PopUpButton*)data;
    int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
    int repeat = 0;
    int dy = 0;


    if (bPtr->scrollStartY >= scrHeight-1
	&& bPtr->menuView->pos.y+bPtr->menuView->size.height >= scrHeight-1) {
	repeat = 1;

	if (bPtr->menuView->pos.y+bPtr->menuView->size.height-5 
	    <= scrHeight - 1) {
	    dy = scrHeight - 1
		- (bPtr->menuView->pos.y+bPtr->menuView->size.height);
	} else
	    dy = -5;

    } else if (bPtr->scrollStartY <= 1 && bPtr->menuView->pos.y < 1) {
	repeat = 1;

	if (bPtr->menuView->pos.y+5 > 1) 
	    dy = 1 - bPtr->menuView->pos.y;
	else
	    dy = 5;
    }

    if (repeat) {
	int oldItem;

	W_MoveView(bPtr->menuView, bPtr->menuView->pos.x, 
		   bPtr->menuView->pos.y + dy);

	oldItem = bPtr->highlightedItem;
	bPtr->highlightedItem = (bPtr->scrollStartY - bPtr->menuView->pos.y)
	    / bPtr->view->size.height;

	if (oldItem!=bPtr->highlightedItem) {
            WMMenuItem *item;

	    paintMenuEntry(bPtr, oldItem, False);

            if (bPtr->highlightedItem >= 0 &&
                bPtr->highlightedItem < WMGetBagItemCount(bPtr->items)) {
                item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
                paintMenuEntry(bPtr, bPtr->highlightedItem,
                               WMGetMenuItemEnabled(item));
            } else {
                bPtr->highlightedItem = -1;
            }
	}

	bPtr->timer = WMAddTimerHandler(SCROLL_DELAY, autoScroll, bPtr);
    } else {
	bPtr->timer = NULL;
    }
}


static void
handleActionEvents(XEvent *event, void *data)
{
    PopUpButton *bPtr = (PopUpButton*)data;
    int oldItem;
    int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;

    CHECK_CLASS(data, WC_PopUpButton);

    if (WMGetBagItemCount(bPtr->items) < 1)
	return;
    
    switch (event->type) {
	/* called for menuView */
     case Expose:
	paintMenuEntry(bPtr, bPtr->highlightedItem, True);
	break;

     case LeaveNotify:
	bPtr->flags.insideMenu = 0;
	if (bPtr->menuView->flags.mapped)
	    paintMenuEntry(bPtr, bPtr->highlightedItem, False);
	bPtr->highlightedItem = -1;
	break;
	
     case EnterNotify:
	bPtr->flags.insideMenu = 1;
	break;

     case MotionNotify:
	if (bPtr->flags.insideMenu) {
	    oldItem = bPtr->highlightedItem;
	    bPtr->highlightedItem = event->xmotion.y / bPtr->view->size.height;
	    if (oldItem!=bPtr->highlightedItem) {
		WMMenuItem *item;
		
		paintMenuEntry(bPtr, oldItem, False);
                if (bPtr->highlightedItem >= 0 &&
                    bPtr->highlightedItem < WMGetBagItemCount(bPtr->items)) {
                    item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
                    paintMenuEntry(bPtr, bPtr->highlightedItem,
                                   WMGetMenuItemEnabled(item));
                } else {
                    bPtr->highlightedItem = -1;
                }

	    }

	    if (event->xmotion.y_root >= scrHeight-1
		|| event->xmotion.y_root <= 1) {
		bPtr->scrollStartY = event->xmotion.y_root;
		if (!bPtr->timer)
		    autoScroll(bPtr);
	    } else if (bPtr->timer) {
		WMDeleteTimerHandler(bPtr->timer);
		bPtr->timer = NULL;
	    }
	}
	break;
	
	/* called for bPtr->view */
     case ButtonPress:
	if (!bPtr->flags.enabled)
	    break;

	popUpMenu(bPtr);
	if (!bPtr->flags.pullsDown) {
	    bPtr->highlightedItem = bPtr->selectedItemIndex;
	    bPtr->flags.insideMenu = 1;
	} else {
	    bPtr->highlightedItem = -1;
	    bPtr->flags.insideMenu = 0;
	}
	XGrabPointer(bPtr->view->screen->display, bPtr->menuView->window,
		     False, ButtonReleaseMask|ButtonMotionMask|EnterWindowMask
		     |LeaveWindowMask, GrabModeAsync, GrabModeAsync, 
		     None, None, CurrentTime);
	break;
 
     case ButtonRelease:
	XUngrabPointer(bPtr->view->screen->display, event->xbutton.time);
	if (!bPtr->flags.pullsDown)
	    popDownMenu(bPtr);

	if (bPtr->timer) {
	    WMDeleteTimerHandler(bPtr->timer);
	    bPtr->timer = NULL;
	}

	if (bPtr->flags.insideMenu && bPtr->highlightedItem>=0) {	
	    WMMenuItem *item;
		
	    item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
	    
            if (WMGetMenuItemEnabled(item)) {
		int i;
                WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem);

		if (bPtr->flags.pullsDown) {
		    for (i=0; i<MENU_BLINK_COUNT; i++) {
			paintMenuEntry(bPtr, bPtr->highlightedItem, False);
			XSync(bPtr->view->screen->display, 0);
			wusleep(MENU_BLINK_DELAY);
			paintMenuEntry(bPtr, bPtr->highlightedItem, True);
			XSync(bPtr->view->screen->display, 0);
			wusleep(MENU_BLINK_DELAY);
		    }
		}
		paintMenuEntry(bPtr, bPtr->highlightedItem, False);
		popDownMenu(bPtr);
                if (bPtr->action)
                    (*bPtr->action)(bPtr, bPtr->clientData);
            }
	}
	if (bPtr->menuView->flags.mapped)
	    popDownMenu(bPtr);
	break;
    }    
}



static void
destroyPopUpButton(PopUpButton *bPtr)
{
    WMMenuItem *item;
    WMBagIterator i;

    if (bPtr->timer) {
	WMDeleteTimerHandler(bPtr->timer);
    }

    WM_ITERATE_BAG(bPtr->items, item, i) {
	WMDestroyMenuItem(item);
    }
    WMFreeBag(bPtr->items);

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

    /* have to destroy explicitly because the popup is a toplevel */
    W_DestroyView(bPtr->menuView);

    wfree(bPtr);
}
