/* editmenu.c - editable menus
 * 
 *  WPrefs - Window Maker Preferences Program
 * 
 *  Copyright (c) 2000-2002 Alfredo K. Kojima
 * 
 *  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.
 */


#include <WINGs/WINGsP.h>
#include <WINGs/WUtil.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>

#include "editmenu.h"

typedef struct W_EditMenuItem {
    W_Class widgetClass;
    WMView *view;

    struct W_EditMenu *parent;

    char *label;
    WMPixmap *pixmap;		       /* pixmap to show at left */

    void *data;
    WMCallback *destroyData;

    struct W_EditMenu *submenu;	       /* if it's a cascade, NULL otherwise */
    
    struct {
	unsigned isTitle:1;
	unsigned isHighlighted:1;
    } flags;
} EditMenuItem;


typedef struct W_EditMenu {
    W_Class widgetClass;
    WMView *view;

    struct W_EditMenu *parent;

    WMArray *items; /* EditMenuItem */
    
    EditMenuItem *selectedItem;
    
    WMTextField *tfield;

    WMButton *closeB;

    int titleHeight;
    int itemHeight;
    
    WEditMenuDelegate *delegate;

    WMTextFieldDelegate *tdelegate;

    /* item dragging */
    int draggedItem;
    int dragX, dragY;
        
    /* only for non-standalone menu */
    WMSize maxSize;
    WMSize minSize;
    
    struct {
	unsigned standalone:1;
	unsigned isTitled:1;

	unsigned acceptsDrop:1;	
	unsigned isFactory:1;
	unsigned isSelectable:1;
	unsigned isEditable:1;

	unsigned isTornOff:1;
	
	unsigned isDragging:1;
	unsigned isEditing:1;
	
	unsigned wasMapped:1;
    } flags;
} EditMenu;



/******************** WEditMenuItem ********************/

static void destroyEditMenuItem(WEditMenuItem *iPtr);
static void paintEditMenuItem(WEditMenuItem *iPtr);
static void handleItemEvents(XEvent *event, void *data);

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


static W_Class EditMenuItemClass = 0;


W_Class
InitEditMenuItem(WMScreen *scr)
{
    /* register our widget with WINGs and get our widget class ID */
    if (!EditMenuItemClass) {
	EditMenuItemClass = W_RegisterUserWidget();
    }

    return EditMenuItemClass;
}


WEditMenuItem*
WCreateEditMenuItem(WMWidget *parent, char *title, Bool isTitle)
{
    WEditMenuItem *iPtr;
    WMScreen *scr = WMWidgetScreen(parent);

    if (!EditMenuItemClass)
	InitEditMenuItem(scr);


    iPtr = wmalloc(sizeof(WEditMenuItem));

    memset(iPtr, 0, sizeof(WEditMenuItem));

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

    iPtr->parent = parent;
    
    iPtr->label = wstrdup(title);
    
    iPtr->flags.isTitle = isTitle;
    
    if (isTitle) {
	WMSetWidgetBackgroundColor(iPtr, WMBlackColor(scr));
    }
    
    WMCreateEventHandler(iPtr->view, ExposureMask|StructureNotifyMask,
			 handleItemEvents, iPtr);
    
    WMCreateEventHandler(iPtr->view, ButtonPressMask|ButtonReleaseMask
			 |ButtonMotionMask, handleItemClick, iPtr);


    return iPtr;
}

char*
WGetEditMenuItemTitle(WEditMenuItem *item)
{
    return item->label;
}

void*
WGetEditMenuItemData(WEditMenuItem *item)
{
    return item->data;
}


void
WSetEditMenuItemData(WEditMenuItem *item, void *data, WMCallback *destroyer)
{
    item->data = data;
    item->destroyData = destroyer;
}


void
WSetEditMenuItemImage(WEditMenuItem *item, WMPixmap *pixmap)
{
    if (item->pixmap)
	WMReleasePixmap(item->pixmap);
    item->pixmap = WMRetainPixmap(pixmap);
}


static void
paintEditMenuItem(WEditMenuItem *iPtr)
{
    WMScreen *scr = WMWidgetScreen(iPtr);
    WMColor *color;
    Window win = W_VIEW(iPtr)->window;
    int w = W_VIEW(iPtr)->size.width;
    int h = W_VIEW(iPtr)->size.height;
    WMFont *font = (iPtr->flags.isTitle ? scr->boldFont : scr->normalFont);

    if (!iPtr->view->flags.realized)
	return;

    color = scr->black;
    if (iPtr->flags.isTitle && !iPtr->flags.isHighlighted) {
	color = scr->white;
    }


    XClearWindow(scr->display, win);

    W_DrawRelief(scr, win, 0, 0, w+1, h, WRRaised);

    WMDrawString(scr, win, WMColorGC(color), font, 5, 3, iPtr->label,
		 strlen(iPtr->label));
    
    if (iPtr->pixmap) {
	WMSize size = WMGetPixmapSize(iPtr->pixmap);
	
	WMDrawPixmap(iPtr->pixmap, win, w - size.width - 5,
		     (h - size.height)/2);
    } else if (iPtr->submenu) {
	/* draw the cascade indicator */
	XDrawLine(scr->display,win,WMColorGC(scr->darkGray), 
		  w-11, 6, w-6, h/2-1);
	XDrawLine(scr->display,win,WMColorGC(scr->white),
		  w-11, h-8, w-6, h/2-1);
	XDrawLine(scr->display,win,WMColorGC(scr->black), 
		  w-12, 6, w-12, h-8);
    }
}


static void
highlightItem(WEditMenuItem *iPtr, Bool high)
{   
    if (iPtr->flags.isTitle)
	return;
    
    iPtr->flags.isHighlighted = high;

    if (high) {
	WMSetWidgetBackgroundColor(iPtr, WMWhiteColor(WMWidgetScreen(iPtr)));
    } else {
	if (!iPtr->flags.isTitle) {
	    WMSetWidgetBackgroundColor(iPtr,
				       WMGrayColor(WMWidgetScreen(iPtr)));
	} else {
	    WMSetWidgetBackgroundColor(iPtr,
				       WMBlackColor(WMWidgetScreen(iPtr)));
	}
    }
}


static int
getItemTextWidth(WEditMenuItem *iPtr)
{
    WMScreen *scr = WMWidgetScreen(iPtr);
    
    if (iPtr->flags.isTitle) {
	return WMWidthOfString(scr->boldFont, iPtr->label, 
			       strlen(iPtr->label));
    } else {
	return WMWidthOfString(scr->normalFont, iPtr->label, 
			       strlen(iPtr->label));
    }
}



static void
handleItemEvents(XEvent *event, void *data)
{
    WEditMenuItem *iPtr = (WEditMenuItem*)data;

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

     case DestroyNotify:
	destroyEditMenuItem(iPtr);
	break;
    }
}


static void
destroyEditMenuItem(WEditMenuItem *iPtr)
{
    if (iPtr->label)
	wfree(iPtr->label);
    if (iPtr->data && iPtr->destroyData)
	(*iPtr->destroyData)(iPtr->data);
    if (iPtr->submenu)
	WMDestroyWidget(iPtr->submenu);

    wfree(iPtr);
}



/******************** WEditMenu *******************/

static void destroyEditMenu(WEditMenu *mPtr);

static void updateMenuContents(WEditMenu *mPtr);

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

static void editItemLabel(WEditMenuItem *item);

static void stopEditItem(WEditMenu *menu, Bool apply);

static void deselectItem(WEditMenu *menu);


static W_Class EditMenuClass = 0;


W_Class
InitEditMenu(WMScreen *scr)
{
    /* register our widget with WINGs and get our widget class ID */
    if (!EditMenuClass) {

	EditMenuClass = W_RegisterUserWidget();
    }

    return EditMenuClass;
}



typedef struct {
    int flags;
    int window_style;
    int window_level;
    int reserved;
    Pixmap miniaturize_pixmap;         /* pixmap for miniaturize button */
    Pixmap close_pixmap;               /* pixmap for close button */
    Pixmap miniaturize_mask;           /* miniaturize pixmap mask */
    Pixmap close_mask;                 /* close pixmap mask */
    int extra_flags;
} GNUstepWMAttributes;


#define GSWindowStyleAttr       (1<<0)
#define GSWindowLevelAttr       (1<<1)


static void
writeGNUstepWMAttr(WMScreen *scr, Window window, GNUstepWMAttributes *attr)
{
    unsigned long data[9];
        
    /* handle idiot compilers where array of CARD32 != struct of CARD32 */
    data[0] = attr->flags;
    data[1] = attr->window_style;
    data[2] = attr->window_level;
    data[3] = 0;                       /* reserved */
    /* The X protocol says XIDs are 32bit */
    data[4] = attr->miniaturize_pixmap;
    data[5] = attr->close_pixmap;
    data[6] = attr->miniaturize_mask;
    data[7] = attr->close_mask;
    data[8] = attr->extra_flags;
    XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom,
                    32, PropModeReplace,  (unsigned char *)data, 9);
}


static void
realizeObserver(void *self, WMNotification *not)
{
    WEditMenu *menu = (WEditMenu*)self;
    GNUstepWMAttributes attribs;

    memset(&attribs, 0, sizeof(GNUstepWMAttributes));
    attribs.flags = GSWindowStyleAttr|GSWindowLevelAttr;
    attribs.window_style = WMBorderlessWindowMask;
    attribs.window_level = WMSubmenuWindowLevel;

    writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs);
}


static void
itemSelectObserver(void *self, WMNotification *notif)
{
    WEditMenu *menu = (WEditMenu*)self;
    WEditMenu *rmenu;

    rmenu = (WEditMenu*)WMGetNotificationObject(notif);
    /* check whether rmenu is from the same hierarchy of menu? */
    
    if (rmenu == menu) {
	return;
    }

    if (menu->selectedItem) {
	if (!menu->selectedItem->submenu)
	    deselectItem(menu);
	if (menu->flags.isEditing)
	    stopEditItem(menu, False);
    }
}


static WEditMenu*
makeEditMenu(WMScreen *scr, WMWidget *parent, char *title)
{
    WEditMenu *mPtr;
    WEditMenuItem *item;

    if (!EditMenuClass)
	InitEditMenu(scr);


    mPtr = wmalloc(sizeof(WEditMenu));
    memset(mPtr, 0, sizeof(WEditMenu));

    mPtr->widgetClass = EditMenuClass;

    if (parent) {
	mPtr->view = W_CreateView(W_VIEW(parent));
	mPtr->flags.standalone = 0;
    } else {
	mPtr->view = W_CreateTopView(scr);
	mPtr->flags.standalone = 1;
    }
    if (!mPtr->view) {
	wfree(mPtr);
	return NULL;
    }
    mPtr->view->self = mPtr;

    mPtr->flags.isSelectable = 1;
    mPtr->flags.isEditable = 1;

    W_SetViewBackgroundColor(mPtr->view, scr->darkGray);

    WMAddNotificationObserver(realizeObserver, mPtr,
                              WMViewRealizedNotification, mPtr->view);

    WMAddNotificationObserver(itemSelectObserver, mPtr,
                              "EditMenuItemSelected", NULL);

    mPtr->items = WMCreateArray(4);

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

    if (title != NULL) {
	item = WCreateEditMenuItem(mPtr, title, True);

	WMMapWidget(item);
	WMAddToArray(mPtr->items, item);

	mPtr->flags.isTitled = 1;
    }

    mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6;
    mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8;

    updateMenuContents(mPtr);
    
    return mPtr;
}


WEditMenu*
WCreateEditMenu(WMScreen *scr, char *title)
{
    return makeEditMenu(scr, NULL, title);
}


WEditMenu*
WCreateEditMenuPad(WMWidget *parent)
{
    return makeEditMenu(WMWidgetScreen(parent), parent, NULL);
}


void
WSetEditMenuDelegate(WEditMenu *mPtr, WEditMenuDelegate *delegate)
{
    mPtr->delegate = delegate;
}


WEditMenuItem*
WInsertMenuItemWithTitle(WEditMenu *mPtr, int index, char *title)
{
    WEditMenuItem *item;

    item = WCreateEditMenuItem(mPtr, title, False);

    WMMapWidget(item);

    if (index >= WMGetArrayItemCount(mPtr->items)) {
	WMAddToArray(mPtr->items, item);
    } else {
	if (index < 0)
	    index = 0;
	if (mPtr->flags.isTitled)
	    index++;
	WMInsertInArray(mPtr->items, index, item);
    }

    updateMenuContents(mPtr);

    return item;
}


WEditMenuItem*
WGetEditMenuItem(WEditMenu *mPtr, int index)
{
    if (index >= WMGetArrayItemCount(mPtr->items))
	return NULL;

    return WMGetFromArray(mPtr->items, index + (mPtr->flags.isTitled ? 1 : 0));
}


WEditMenuItem*
WAddMenuItemWithTitle(WEditMenu *mPtr, char *title)
{
    return WInsertMenuItemWithTitle(mPtr, WMGetArrayItemCount(mPtr->items),
				    title);
}



void
WSetEditMenuTitle(WEditMenu *mPtr, char *title)
{
    WEditMenuItem *item;

    item = WMGetFromArray(mPtr->items, 0);

    wfree(item->label);
    item->label = wstrdup(title);

    updateMenuContents(mPtr);

    WMRedisplayWidget(item);
}



char*
WGetEditMenuTitle(WEditMenu *mPtr)
{
    WEditMenuItem *item;

    item = WMGetFromArray(mPtr->items, 0);

    return item->label;
}


void
WSetEditMenuAcceptsDrop(WEditMenu *mPtr, Bool flag)
{
    mPtr->flags.acceptsDrop = flag;
}


void
WSetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item, WEditMenu *submenu)
{
    item->submenu = submenu;
    submenu->parent = mPtr;

    paintEditMenuItem(item);
}


WEditMenu*
WGetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item)
{
    return item->submenu;
}


void
WRemoveEditMenuItem(WEditMenu *mPtr, WEditMenuItem *item)
{
    if (WMRemoveFromArray(mPtr->items, item) != 0) {
        updateMenuContents(mPtr);
    }
}


void
WSetEditMenuSelectable(WEditMenu *mPtr, Bool flag)
{
    mPtr->flags.isSelectable = flag;
}


void
WSetEditMenuEditable(WEditMenu *mPtr, Bool flag)
{
    mPtr->flags.isEditable = flag;
}


void
WSetEditMenuIsFactory(WEditMenu *mPtr, Bool flag)
{
    mPtr->flags.isFactory = flag;
}


void
WSetEditMenuMinSize(WEditMenu *mPtr, WMSize size)
{
    mPtr->minSize.width = size.width;
    mPtr->minSize.height = size.height;
}


void
WSetEditMenuMaxSize(WEditMenu *mPtr, WMSize size)
{
    mPtr->maxSize.width = size.width;
    mPtr->maxSize.height = size.height;
}


WMPoint
WGetEditMenuLocationForSubmenu(WEditMenu *mPtr, WEditMenu *submenu)
{
    WMArrayIterator iter;
    WEditMenuItem *item;
    int y;

    if (mPtr->flags.isTitled)
	y = -mPtr->titleHeight;
    else
	y = 0;
    WM_ITERATE_ARRAY(mPtr->items, item, iter) {
	if (item->submenu == submenu) {
	    WMPoint pt = WMGetViewScreenPosition(mPtr->view);

	    return wmkpoint(pt.x + mPtr->view->size.width, pt.y + y);
	}
	y += W_VIEW_HEIGHT(item->view);
    }

    puts("invalid submenu passed to WGetEditMenuLocationForSubmenu()");
    
    return wmkpoint(0,0);
}



static void
closeMenuAction(WMWidget *w, void *data)
{
    WEditMenu *menu = (WEditMenu*)data;

    WMAddIdleHandler(WMDestroyWidget, menu->closeB);
    menu->closeB = NULL;

    WEditMenuHide(menu);
}


void
WTearOffEditMenu(WEditMenu *menu, WEditMenu *submenu)
{
    WEditMenuItem *item;
    
    submenu->flags.isTornOff = 1;
    
    item = (WEditMenuItem*)WMGetFromArray(submenu->items, 0);
    
    submenu->closeB = WMCreateCommandButton(item);
    WMResizeWidget(submenu->closeB, 15, 15);
    WMMoveWidget(submenu->closeB, W_VIEW(submenu)->size.width - 20, 3);
    WMRealizeWidget(submenu->closeB);
    WMSetButtonText(submenu->closeB, "X");
    WMSetButtonAction(submenu->closeB, closeMenuAction, submenu);
    WMMapWidget(submenu->closeB);

    if (menu->selectedItem && menu->selectedItem->submenu == submenu)
	deselectItem(menu);
}
						    


Bool
WEditMenuIsTornOff(WEditMenu *mPtr)
{
    return mPtr->flags.isTornOff;
}



void
WEditMenuHide(WEditMenu *mPtr)
{
    WEditMenuItem *item;
    int i = 0;

    if (WMWidgetIsMapped(mPtr)) {
	WMUnmapWidget(mPtr);
	mPtr->flags.wasMapped = 1;
    } else {
	mPtr->flags.wasMapped = 0;
    }
    while ((item = WGetEditMenuItem(mPtr, i++))) {
	WEditMenu *submenu;

	submenu = WGetEditMenuSubmenu(mPtr, item);
	if (submenu) {
	    WEditMenuHide(submenu);
	}
    }
}



void
WEditMenuUnhide(WEditMenu *mPtr)
{
    WEditMenuItem *item;
    int i = 0;

    if (mPtr->flags.wasMapped) {
	WMMapWidget(mPtr);
    }
    while ((item = WGetEditMenuItem(mPtr, i++))) {
	WEditMenu *submenu;

	submenu = WGetEditMenuSubmenu(mPtr, item);
	if (submenu) {
	    WEditMenuUnhide(submenu);
	}
    }
}


void
WEditMenuShowAt(WEditMenu *menu, int x, int y)
{
    XSizeHints *hints;

    hints = XAllocSizeHints();

    hints->flags = USPosition;
    hints->x = x;
    hints->y = y;

    WMMoveWidget(menu, x, y);
    XSetWMNormalHints(W_VIEW_DISPLAY(menu->view),
		      W_VIEW_DRAWABLE(menu->view),
		      hints);
    XFree(hints);

    WMMapWidget(menu);
}


static void
updateMenuContents(WEditMenu *mPtr)
{
    int newW, newH;
    int w;
    int i;
    int iheight = mPtr->itemHeight;
    int offs = 1;
    WMArrayIterator iter;
    WEditMenuItem *item;

    newW = 0;
    newH = offs;

    i = 0;
    WM_ITERATE_ARRAY(mPtr->items, item, iter) {	
	w = getItemTextWidth(item);

	newW = WMAX(w, newW);

	WMMoveWidget(item, offs, newH);
	if (i == 0 && mPtr->flags.isTitled) {
	    newH += mPtr->titleHeight;
	} else {
	    newH += iheight;
	}
	i = 1;
    }

    newW += iheight + 10;
    newH--;
    
    if (mPtr->minSize.width)
	newW = WMAX(newW, mPtr->minSize.width);
    if (mPtr->maxSize.width)
	newW = WMIN(newW, mPtr->maxSize.width); 
   
    if (mPtr->minSize.height)
	newH = WMAX(newH, mPtr->minSize.height);
    if (mPtr->maxSize.height)
	newH = WMIN(newH, mPtr->maxSize.height);
    
    if (W_VIEW(mPtr)->size.width == newW && mPtr->view->size.height == newH+1)
	return;

    W_ResizeView(mPtr->view, newW, newH+1);

    if (mPtr->closeB)
	WMMoveWidget(mPtr->closeB, newW - 20, 3);

    newW -= 2*offs;
    
    i = 0;
    WM_ITERATE_ARRAY(mPtr->items, item, iter) {
	if (i == 0 && mPtr->flags.isTitled) {
	    WMResizeWidget(item, newW, mPtr->titleHeight);	    
	} else {
	    WMResizeWidget(item, newW, iheight);
	}
	i = 1;
    }
}


static void
deselectItem(WEditMenu *menu)
{
    WEditMenu *submenu;
    WEditMenuItem *item = menu->selectedItem;
    
    highlightItem(item, False);
    
    if (menu->delegate && menu->delegate->itemDeselected) {
	(*menu->delegate->itemDeselected)(menu->delegate, menu, item);
    }
    submenu = item->submenu;

    if (submenu && !WEditMenuIsTornOff(submenu)) {
	WEditMenuHide(submenu);
    }
    
    menu->selectedItem = NULL;
}


static void
selectItem(WEditMenu *menu, WEditMenuItem *item)
{
    if (!menu->flags.isSelectable || menu->selectedItem == item) {
	return;
    }
    if (menu->selectedItem) {
	deselectItem(menu);
    }

    if (menu->flags.isEditing) {
	stopEditItem(menu, False);
    }

    if (item && !item->flags.isTitle) {
	highlightItem(item, True);

	if (item->submenu && !W_VIEW_MAPPED(item->submenu->view)) {
	    WMPoint pt;

	    pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
	    
	    WEditMenuShowAt(item->submenu, pt.x, pt.y);
	    
	    item->submenu->flags.isTornOff = 0;
	}

	WMPostNotificationName("EditMenuItemSelected", menu, NULL);

	if (menu->delegate && menu->delegate->itemSelected) {
	    (*menu->delegate->itemSelected)(menu->delegate, menu, item);
	}
    }

    menu->selectedItem = item;
}


static void
paintMenu(WEditMenu *mPtr)
{
    W_View *view = mPtr->view;
    
    W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
		 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
}


static void
handleEvents(XEvent *event, void *data)
{
    WEditMenu *mPtr = (WEditMenu*)data;

    switch (event->type) {	
     case DestroyNotify:
	destroyEditMenu(mPtr);
	break;
	
     case Expose:
	if (event->xexpose.count == 0)
	    paintMenu(mPtr);
	break;
    }
}




/* -------------------------- Menu Label Editing ------------------------ */


static void
stopEditItem(WEditMenu *menu, Bool apply)
{
    char *text;
    
    if (apply) {
	text = WMGetTextFieldText(menu->tfield);
	
	wfree(menu->selectedItem->label);
	menu->selectedItem->label = wstrdup(text);

	updateMenuContents(menu);

	if (menu->delegate && menu->delegate->itemEdited) {
	    (*menu->delegate->itemEdited)(menu->delegate, menu,
					  menu->selectedItem);
	}

    } 
    WMUnmapWidget(menu->tfield);
    menu->flags.isEditing = 0;
}


static void
textEndedEditing(struct WMTextFieldDelegate *self, WMNotification *notif)
{
    WEditMenu *menu = (WEditMenu*)self->data;
    int reason;
    int i;
    WEditMenuItem *item;
    
    if (!menu->flags.isEditing)
	return;

    reason = (int)WMGetNotificationClientData(notif);
    
    switch (reason) {	
     case WMEscapeTextMovement:
	stopEditItem(menu, False);
	break;
	
     case WMReturnTextMovement:
	stopEditItem(menu, True);
	break;
	
     case WMTabTextMovement:
	stopEditItem(menu, True);

	i = WMGetFirstInArray(menu->items, menu->selectedItem);
	item = WMGetFromArray(menu->items, i+1);
	if (item != NULL) {
	    selectItem(menu, item);
	    editItemLabel(item);
	}
	break;
	
     case WMBacktabTextMovement:
	stopEditItem(menu, True);

	i = WMGetFirstInArray(menu->items, menu->selectedItem);
	item = WMGetFromArray(menu->items, i-1);
	if (item != NULL) {
	    selectItem(menu, item);
	    editItemLabel(item);
	}
	break;
    }
}



static WMTextFieldDelegate textFieldDelegate = {
    NULL,
	NULL,
	NULL,
	textEndedEditing,
	NULL,
	NULL
};


static void
editItemLabel(WEditMenuItem *item)
{
    WEditMenu *mPtr = item->parent;
    WMTextField *tf;
    
    if (!mPtr->flags.isEditable) {
	return;
    }

    if (!mPtr->tfield) {
	mPtr->tfield = WMCreateTextField(mPtr);
	WMSetTextFieldBeveled(mPtr->tfield, False);
	WMRealizeWidget(mPtr->tfield);

	mPtr->tdelegate = wmalloc(sizeof(WMTextFieldDelegate));
	memcpy(mPtr->tdelegate, &textFieldDelegate,
	       sizeof(WMTextFieldDelegate));
	
	mPtr->tdelegate->data = mPtr;

	WMSetTextFieldDelegate(mPtr->tfield, mPtr->tdelegate);
    }
    tf = mPtr->tfield;

    WMSetTextFieldText(tf, item->label);
    WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
    WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
    WMMoveWidget(tf, 0, item->view->pos.y);
    WMMapWidget(tf);
    WMSetFocusToWidget(tf);

    mPtr->flags.isEditing = 1;
}



/* -------------------------------------------------- */


static void
slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
{
    double x, y, dx, dy;
    int i;
    int iterations;

    iterations = WMIN(25, WMAX(abs(dstX-srcX), abs(dstY-srcY)));
    
    x = srcX;
    y = srcY;
    
    dx = (double)(dstX-srcX)/iterations;
    dy = (double)(dstY-srcY)/iterations;

    for (i = 0; i <= iterations; i++) {
	XMoveWindow(dpy, win, x, y);
        XFlush(dpy);
        
	wusleep(800);
	
        x += dx;
        y += dy;
    }
}


static int
errorHandler(Display *d, XErrorEvent *ev)
{
    /* just ignore */
    return 0;
}


static WEditMenu*
findMenuInWindow(Display *dpy, Window toplevel, int x, int y)
{
    Window foo, bar;
    Window *children;
    unsigned nchildren;
    int i;
    WEditMenu *menu;
    WMView *view;
    int (*oldHandler)(Display *, XErrorEvent *);
    
    view = W_GetViewForXWindow(dpy, toplevel);
    if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
	menu = (WEditMenu*)view->self;
	if (menu->flags.acceptsDrop) {
	    return menu;
	}
    }
    
    if (!XQueryTree(dpy, toplevel, &foo, &bar,
		    &children, &nchildren) || children == NULL) {
	return NULL;
    }
    
    oldHandler = XSetErrorHandler(errorHandler);
    
    /* first window that contains the point is the one */
    for (i = nchildren-1; i >= 0; i--) {
	XWindowAttributes attr;

	if (XGetWindowAttributes(dpy, children[i], &attr)
	    && attr.map_state == IsViewable
	    && x >= attr.x && y >= attr.y
	    && x < attr.x + attr.width && y < attr.y + attr.height) {
	    Window tmp;

	    tmp = children[i];

	    menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
	    if (menu) {
		XFree(children);
		return menu;
	    }
	}
    }
    
    XSetErrorHandler(oldHandler);

    XFree(children);
    return NULL;
}


static void
handleDragOver(WEditMenu *menu, WMView *view, WEditMenuItem *item, 
	       int x, int y)
{
    WMScreen *scr = W_VIEW_SCREEN(menu->view);
    Window blaw;
    int mx, my;
    int offs;

    XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
			  scr->rootWin, 0, 0, &mx, &my, &blaw);
    
    offs = menu->flags.standalone ? 0 : 1;
    
    W_MoveView(view, mx + offs, y);
    if (view->size.width != menu->view->size.width) {
	W_ResizeView(view, menu->view->size.width - 2*offs, 
		     menu->itemHeight);
	W_ResizeView(item->view, menu->view->size.width - 2*offs,
		     menu->itemHeight);
    }
}


static void
handleItemDrop(WEditMenu *menu, WEditMenuItem *item, int x, int y)
{ 
    WMScreen *scr = W_VIEW_SCREEN(menu->view);
    Window blaw;
    int mx, my;
    int index;

    XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
			  scr->rootWin, 0, 0, &mx, &my, &blaw);

    index = y - my;
    if (menu->flags.isTitled) {
	index -= menu->titleHeight;
    }
    index = (index + menu->itemHeight/2) / menu->itemHeight;
    if (index < 0)
	index = 0;

    if (menu->flags.isTitled) {
	index++;
    }
    
    if (index > WMGetArrayItemCount(menu->items)) {
	WMAddToArray(menu->items, item);
    } else {
	WMInsertInArray(menu->items, index, item);
    }

    W_ReparentView(item->view, menu->view, 0, index*menu->itemHeight);
    
    item->parent = menu;
    if (item->submenu) {
	item->submenu->parent = menu;
    }

    updateMenuContents(menu);
}


static void
dragMenu(WEditMenu *menu)
{
    WMScreen *scr = W_VIEW_SCREEN(menu->view);
    XEvent ev;
    Bool done = False;
    int dx, dy;
    unsigned blau;
    Window blaw;
    
    XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy,
		 &blau, &blau, &blau, &blau);

    XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
			  scr->rootWin, dx, dy, &dx, &dy, &blaw);

    dx = menu->dragX - dx;
    dy = menu->dragY - dy;
    
    XGrabPointer(scr->display, scr->rootWin, False,
		 ButtonReleaseMask|ButtonMotionMask,
		 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
		 CurrentTime);
    
    if (menu->parent)
	WTearOffEditMenu(menu->parent, menu);

    while (!done) {
	WMNextEvent(scr->display, &ev);
	
	switch (ev.type) {
	 case ButtonRelease:
	    done = True;
	    break;

	 case MotionNotify:
	    while (XCheckMaskEvent(scr->display, ButtonMotionMask, &ev)) ;

	    WMMoveWidget(menu, ev.xmotion.x_root - dx,
			 ev.xmotion.y_root - dy);
	    break;

	 default:
	    WMHandleEvent(&ev);
	    break;
	}
    }

    XUngrabPointer(scr->display, CurrentTime);
}



static WEditMenuItem*
duplicateItem(WEditMenuItem *item)
{
    WEditMenuItem *nitem;
    
    nitem = WCreateEditMenuItem(item->parent, item->label, False);
    if (item->pixmap)
	nitem->pixmap = WMRetainPixmap(item->pixmap);

    return nitem;
}




static WEditMenu*
duplicateMenu(WEditMenu *menu)
{
    WEditMenu *nmenu;
    WEditMenuItem *item;
    WMArrayIterator iter;
    Bool first = menu->flags.isTitled;

    nmenu = WCreateEditMenu(WMWidgetScreen(menu), WGetEditMenuTitle(menu));

    memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
    nmenu->delegate = menu->delegate;
    
    WM_ITERATE_ARRAY(menu->items, item, iter) {
	WEditMenuItem *nitem;
	
	if (first) {
	    first = False;
	    continue;
	}

	nitem = WAddMenuItemWithTitle(nmenu, item->label);
	if (item->pixmap)
	    WSetEditMenuItemImage(nitem, item->pixmap);

	if (menu->delegate && menu->delegate->itemCloned) {
	    (*menu->delegate->itemCloned)(menu->delegate, menu,
					  item, nitem);
	}
    }

    WMRealizeWidget(nmenu);

    return nmenu;
}



static void
dragItem(WEditMenu *menu, WEditMenuItem *item, Bool copy)
{
    static XColor black = {0, 0,0,0, DoRed|DoGreen|DoBlue};
    static XColor green = {0x0045b045, 0x4500,0xb000,0x4500, DoRed|DoGreen|DoBlue};
    static XColor back = {0, 0xffff,0xffff,0xffff, DoRed|DoGreen|DoBlue};
    Display *dpy = W_VIEW_DISPLAY(menu->view);
    WMScreen *scr = W_VIEW_SCREEN(menu->view);
    int x, y;
    int dx, dy;
    Bool done = False;
    Window blaw;
    int blai;
    unsigned blau;
    Window win;
    WMView *dview;
    int orix, oriy;
    Bool enteredMenu = False;
    WMSize oldSize = item->view->size;
    WEditMenuItem *dritem = item;
    WEditMenu *dmenu = NULL;


    if (item->flags.isTitle) {
	WMRaiseWidget(menu);

	dragMenu(menu);

	return;
    }
    
    selectItem(menu, NULL);

    win = scr->rootWin;
    
    XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win,
			  0, 0, &orix, &oriy, &blaw);

    dview = W_CreateUnmanagedTopView(scr);
    W_SetViewBackgroundColor(dview, scr->black);
    W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
    W_MoveView(dview, orix, oriy);
    W_RealizeView(dview);
    
    if (menu->flags.isFactory || copy) {
	dritem = duplicateItem(item);

	W_ReparentView(dritem->view, dview, 0, 0);
	WMResizeWidget(dritem, oldSize.width, oldSize.height);
	WMRealizeWidget(dritem);
	WMMapWidget(dritem);
    } else {
	W_ReparentView(item->view, dview, 0, 0);
    }

    W_MapView(dview);

    dx = menu->dragX - orix;
    dy = menu->dragY - oriy;
    
    XGrabPointer(dpy, scr->rootWin, False,
		 ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
		 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
		 CurrentTime);

    if (menu->flags.acceptsDrop)
	XRecolorCursor(dpy, scr->defaultCursor, &green, &back);

    while (!done) {
	XEvent ev;

	WMNextEvent(dpy, &ev);

	switch (ev.type) {
	 case MotionNotify:
	    while (XCheckMaskEvent(dpy, ButtonMotionMask, &ev)) ;

	    XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);

	    dmenu = findMenuInWindow(dpy, win, x, y);

	    if (dmenu) {
		handleDragOver(dmenu, dview, dritem, x - dx, y - dy);
		if (!enteredMenu) {
		    enteredMenu = True;
		    XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
		}
	    } else {
		if (enteredMenu) {
		    W_ResizeView(dview, oldSize.width, oldSize.height);
		    W_ResizeView(dritem->view, oldSize.width, oldSize.height);
		    enteredMenu = False;
		    XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
		}
		W_MoveView(dview, x - dx, y - dy);
	    }
	    
	    break;
	    
	 case ButtonRelease:
	    done = True;
	    break;
	    
	 default:
	    WMHandleEvent(&ev);
	    break;
	}
    }
    XRecolorCursor(dpy, scr->defaultCursor, &black, &back);

    XUngrabPointer(dpy, CurrentTime);

    
    if (!enteredMenu) {
	Bool rem = True;
	
	if (!menu->flags.isFactory && !copy) {
	    W_UnmapView(dview);
	    if (menu->delegate && menu->delegate->shouldRemoveItem) {
		rem = (*menu->delegate->shouldRemoveItem)(menu->delegate,
							  menu, item);
	    }
	    W_MapView(dview);
	}

	if (!rem || menu->flags.isFactory || copy) {
	    slideWindow(dpy, W_VIEW_DRAWABLE(dview), x-dx, y-dy, orix, oriy);

	    if (!menu->flags.isFactory && !copy) {
		WRemoveEditMenuItem(menu, dritem);
		handleItemDrop(dmenu ? dmenu : menu, dritem, orix, oriy);
	    }
	} else {
	    WRemoveEditMenuItem(menu, dritem);
	}
    } else {
	WRemoveEditMenuItem(menu, dritem);

	if (menu->delegate && menu->delegate->itemCloned
	    && (menu->flags.isFactory || copy)) {
	    (*menu->delegate->itemCloned)(menu->delegate, menu,
					  item, dritem);
	}
	
	handleItemDrop(dmenu, dritem, x-dy, y-dy);
	
	if (item->submenu && (menu->flags.isFactory || copy)) {
	    WEditMenu *submenu;
	    
	    submenu = duplicateMenu(item->submenu);
	    WSetEditMenuSubmenu(dmenu, dritem, submenu);
	}
    }

    /* can't destroy now because we're being called from 
     * the event handler of the item. and destroying now,
     * would mean destroying the item too in some cases.
     */
    WMAddIdleHandler((WMCallback*)W_DestroyView, dview);
}



static void
handleItemClick(XEvent *event, void *data)
{
    WEditMenuItem *item = (WEditMenuItem*)data;
    WEditMenu *menu = item->parent;

    switch (event->type) {
     case ButtonPress:
	selectItem(menu, item);

	if (WMIsDoubleClick(event)) {
	    editItemLabel(item);
	}
	
	menu->flags.isDragging = 1;
	menu->dragX = event->xbutton.x_root;
	menu->dragY = event->xbutton.y_root;
	break;
	
     case ButtonRelease:
	menu->flags.isDragging = 0;
	break;
	
     case MotionNotify:
	if (menu->flags.isDragging) {
	    if (abs(event->xbutton.x_root - menu->dragX) > 5
		|| abs(event->xbutton.y_root - menu->dragY) > 5) {
		menu->flags.isDragging = 0;
		dragItem(menu, item, event->xbutton.state & ControlMask);
	    }
	}
	break;
    }
}


static void
destroyEditMenu(WEditMenu *mPtr)
{
    WEditMenuItem *item;
    WMArrayIterator iter;

    WMRemoveNotificationObserver(mPtr);

    WM_ITERATE_ARRAY(mPtr->items, item, iter) {
	if (item->submenu) {
	    WMDestroyWidget(item->submenu);
	}
    }
    
    WMFreeArray(mPtr->items);
    
    wfree(mPtr->tdelegate);
    
    wfree(mPtr);
}



