view src/gui_athena.c @ 26177:13e09dc59f0f v8.2.3620

patch 8.2.3620: memory leak reported in libtlib Commit: https://github.com/vim/vim/commit/b3a29558eedac9979f5276ae914934b7b7cbe06b Author: Bram Moolenaar <Bram@vim.org> Date: Fri Nov 19 11:28:04 2021 +0000 patch 8.2.3620: memory leak reported in libtlib Problem: Memory leak reported in libtlib. Solution: Call del_curterm() when cleaning up memory. Rename term.h to termdefs.h to avoid a name clash.
author Bram Moolenaar <Bram@vim.org>
date Fri, 19 Nov 2021 12:30:05 +0100
parents 7334bf933510
children fc859aea8cec
line wrap: on
line source

/* vi:set ts=8 sts=4 sw=4 noet:
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *				GUI/Motif support by Robert Webb
 *				Athena port by Bill Foster
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 * See README.txt for an overview of the Vim source code.
 */

#include "vim.h"

#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#ifdef FEAT_GUI_NEXTAW
# include <X11/neXtaw/Form.h>
# include <X11/neXtaw/SimpleMenu.h>
# include <X11/neXtaw/MenuButton.h>
# include <X11/neXtaw/SmeBSB.h>
# include <X11/neXtaw/SmeLine.h>
# include <X11/neXtaw/Box.h>
# include <X11/neXtaw/Dialog.h>
# include <X11/neXtaw/Text.h>
# include <X11/neXtaw/AsciiText.h>
# include <X11/neXtaw/Scrollbar.h>
#else
# include <X11/Xaw/Form.h>
# include <X11/Xaw/SimpleMenu.h>
# include <X11/Xaw/MenuButton.h>
# include <X11/Xaw/SmeBSB.h>
# include <X11/Xaw/SmeLine.h>
# include <X11/Xaw/Box.h>
# include <X11/Xaw/Dialog.h>
# include <X11/Xaw/Text.h>
# include <X11/Xaw/AsciiText.h>
#endif // FEAT_GUI_NEXTAW

#ifndef FEAT_GUI_NEXTAW
# include "gui_at_sb.h"
#endif

extern Widget vimShell;

static Widget vimForm = (Widget)0;
Widget textArea = (Widget)0;
#ifdef FEAT_MENU
static Widget menuBar = (Widget)0;
static XtIntervalId timer = 0;	    // 0 = expired, otherwise active

// Used to figure out menu ordering
static vimmenu_T *a_cur_menu = NULL;
static Cardinal	athena_calculate_ins_pos(Widget);

static void gui_athena_popup_callback(Widget, XtPointer, XtPointer);
static void gui_athena_delayed_arm_action(Widget, XEvent *, String *,
						 Cardinal *);
static void gui_athena_popdown_submenus_action(Widget, XEvent *,
						      String *, Cardinal *);
static XtActionsRec	pullAction[2] = {
    { "menu-delayedpopup", (XtActionProc)gui_athena_delayed_arm_action},
    { "menu-popdownsubmenus", (XtActionProc)gui_athena_popdown_submenus_action}
};
#endif

#ifdef FEAT_TOOLBAR
static void gui_mch_reset_focus(void);
static Widget toolBar = (Widget)0;
#endif

#if defined(FEAT_GUI_DIALOG) || defined(FEAT_MENU)
static void gui_athena_menu_colors(Widget id);
#endif
static void gui_athena_scroll_colors(Widget id);

#ifdef FEAT_MENU
static XtTranslations	popupTrans, parentTrans, menuTrans, supermenuTrans;
static Pixmap		pullerBitmap = None;
static int		puller_width = 0;
#endif

/*
 * Scrollbar callback (XtNjumpProc) for when the scrollbar is dragged with the
 * left or middle mouse button.
 */
    static void
gui_athena_scroll_cb_jump(
    Widget	w UNUSED,
    XtPointer	client_data,
    XtPointer	call_data)
{
    scrollbar_T *sb, *sb_info;
    long	value;

    sb = gui_find_scrollbar((long)client_data);

    if (sb == NULL)
	return;
    else if (sb->wp != NULL)	    // Left or right scrollbar
    {
	/*
	 * Careful: need to get scrollbar info out of first (left) scrollbar
	 * for window, but keep real scrollbar too because we must pass it to
	 * gui_drag_scrollbar().
	 */
	sb_info = &sb->wp->w_scrollbars[0];
    }
    else	    // Bottom scrollbar
	sb_info = sb;

    value = (long)(*((float *)call_data) * (float)(sb_info->max + 1) + 0.001);
    if (value > sb_info->max)
	value = sb_info->max;

    gui_drag_scrollbar(sb, value, TRUE);
}

/*
 * Scrollbar callback (XtNscrollProc) for paging up or down with the left or
 * right mouse buttons.
 */
    static void
gui_athena_scroll_cb_scroll(
    Widget	w UNUSED,
    XtPointer	client_data,
    XtPointer	call_data)
{
    scrollbar_T *sb, *sb_info;
    long	value;
    int		data = (int)(long)call_data;
    int		page;

    sb = gui_find_scrollbar((long)client_data);

    if (sb == NULL)
	return;
    if (sb->wp != NULL)		// Left or right scrollbar
    {
	/*
	 * Careful: need to get scrollbar info out of first (left) scrollbar
	 * for window, but keep real scrollbar too because we must pass it to
	 * gui_drag_scrollbar().
	 */
	sb_info = &sb->wp->w_scrollbars[0];

	if (sb_info->size > 5)
	    page = sb_info->size - 2;	    // use two lines of context
	else
	    page = sb_info->size;
#ifdef FEAT_GUI_NEXTAW
	if (data < 0)
	{
	    data = (data - gui.char_height + 1) / gui.char_height;
	    if (data > -sb_info->size)
		data = -1;
	    else
		data = -page;
	}
	else if (data > 0)
	{
	    data = (data + gui.char_height - 1) / gui.char_height;
	    if (data < sb_info->size)
		data = 1;
	    else
		data = page;
	}
#else
	switch (data)
	{
	    case  ONE_LINE_DATA: data = 1; break;
	    case -ONE_LINE_DATA: data = -1; break;
	    case  ONE_PAGE_DATA: data = page; break;
	    case -ONE_PAGE_DATA: data = -page; break;
	    case  END_PAGE_DATA: data = sb_info->max; break;
	    case -END_PAGE_DATA: data = -sb_info->max; break;
			default: data = 0; break;
	}
#endif
    }
    else			// Bottom scrollbar
    {
	sb_info = sb;
#ifdef FEAT_GUI_NEXTAW
	if (data < 0)
	{
	    data = (data - gui.char_width + 1) / gui.char_width;
	    if (data > -sb->size)
		data = -1;
	}
	else if (data > 0)
	{
	    data = (data + gui.char_width - 1) / gui.char_width;
	    if (data < sb->size)
		data = 1;
	}
#endif
	if (data < -1)		// page-width left
	{
	    if (sb->size > 8)
		data = -(sb->size - 5);
	    else
		data = -sb->size;
	}
	else if (data > 1)	// page-width right
	{
	    if (sb->size > 8)
		data = (sb->size - 5);
	    else
		data = sb->size;
	}
    }

    value = sb_info->value + data;
    if (value > sb_info->max)
	value = sb_info->max;
    else if (value < 0)
	value = 0;

    // Update the bottom scrollbar an extra time (why is this needed??
    if (sb->wp == NULL)		// Bottom scrollbar
	gui_mch_set_scrollbar_thumb(sb, value, sb->size, sb->max);

    gui_drag_scrollbar(sb, value, FALSE);
}

/*
 * Create all the Athena widgets necessary.
 */
    void
gui_x11_create_widgets(void)
{
    /*
     * We don't have any borders handled internally by the textArea to worry
     * about so only skip over the configured border width.
     */
    gui.border_offset = gui.border_width;

    // The form containing all the other widgets
    vimForm = XtVaCreateManagedWidget("vimForm",
	formWidgetClass,	vimShell,
	XtNborderWidth,		0,
	NULL);
    gui_athena_scroll_colors(vimForm);

#ifdef FEAT_MENU
    // The top menu bar
    menuBar = XtVaCreateManagedWidget("menuBar",
	boxWidgetClass,		vimForm,
	XtNresizable,		True,
	XtNtop,			XtChainTop,
	XtNbottom,		XtChainTop,
	XtNleft,		XtChainLeft,
	XtNright,		XtChainRight,
	XtNinsertPosition,	athena_calculate_ins_pos,
	NULL);
    gui_athena_menu_colors(menuBar);
    if (gui.menu_fg_pixel != INVALCOLOR)
	XtVaSetValues(menuBar, XtNborderColor, gui.menu_fg_pixel, NULL);
#endif

#ifdef FEAT_TOOLBAR
    // Don't create it Managed, it will be managed when creating the first
    // item.  Otherwise an empty toolbar shows up.
    toolBar = XtVaCreateWidget("toolBar",
	boxWidgetClass,		vimForm,
	XtNresizable,		True,
	XtNtop,			XtChainTop,
	XtNbottom,		XtChainTop,
	XtNleft,		XtChainLeft,
	XtNright,		XtChainRight,
	XtNorientation,		XtorientHorizontal,
	XtNhSpace,		1,
	XtNvSpace,		3,
	XtNinsertPosition,	athena_calculate_ins_pos,
	NULL);
    gui_athena_menu_colors(toolBar);
#endif

    // The text area.
    textArea = XtVaCreateManagedWidget("textArea",
	coreWidgetClass,	vimForm,
	XtNresizable,		True,
	XtNtop,			XtChainTop,
	XtNbottom,		XtChainTop,
	XtNleft,		XtChainLeft,
	XtNright,		XtChainLeft,
	XtNbackground,		gui.back_pixel,
	XtNborderWidth,		0,
	NULL);

    /*
     * Install the callbacks.
     */
    gui_x11_callbacks(textArea, vimForm);

#ifdef FEAT_MENU
    popupTrans = XtParseTranslationTable(
	    "<EnterWindow>: menu-popdownsubmenus() highlight() menu-delayedpopup()\n"
	    "<LeaveWindow>: unhighlight()\n"
	    "<BtnUp>: menu-popdownsubmenus() XtMenuPopdown() notify() unhighlight()\n"
	    "<Motion>: highlight() menu-delayedpopup()");
    parentTrans = XtParseTranslationTable("<LeaveWindow>: unhighlight()");
    menuTrans = XtParseTranslationTable(
	    "<EnterWindow>: menu-popdownsubmenus() highlight() menu-delayedpopup()\n"
	    "<LeaveWindow>: menu-popdownsubmenus() XtMenuPopdown() unhighlight()\n"
	    "<BtnUp>: notify() unhighlight()\n"
	    "<BtnMotion>: highlight() menu-delayedpopup()");
    supermenuTrans = XtParseTranslationTable(
	    "<EnterWindow>: menu-popdownsubmenus() highlight() menu-delayedpopup()\n"
	    "<LeaveWindow>: unhighlight()\n"
	    "<BtnUp>: menu-popdownsubmenus() XtMenuPopdown() notify() unhighlight()\n"
	    "<BtnMotion>: highlight() menu-delayedpopup()");

    XtAppAddActions(XtWidgetToApplicationContext(vimForm), pullAction,
		    XtNumber(pullAction));
#endif

    // Pretend we don't have input focus, we will get an event if we do.
    gui.in_focus = FALSE;
}

#ifdef FEAT_MENU
/*
 * Calculates the Pixmap based on the size of the current menu font.
 */
    static Pixmap
gui_athena_create_pullright_pixmap(Widget w)
{
    Pixmap  retval;
#ifdef FONTSET_ALWAYS
    XFontSet	font = None;
#else
    XFontStruct	*font = NULL;
#endif

#ifdef FONTSET_ALWAYS
    if (gui.menu_fontset == NOFONTSET)
#else
    if (gui.menu_font == NOFONT)
#endif
    {
	XrmValue from, to;
	WidgetList  children;
	Cardinal    num_children;

#ifdef FONTSET_ALWAYS
	from.size = strlen(from.addr = XtDefaultFontSet);
	to.addr = (XtPointer)&font;
	to.size = sizeof(XFontSet);
#else
	from.size = strlen(from.addr = XtDefaultFont);
	to.addr = (XtPointer)&font;
	to.size = sizeof(XFontStruct *);
#endif
	// Assumption: The menuBar children will use the same font as the
	//	       pulldown menu items AND they will all be of type
	//	       XtNfont.
	XtVaGetValues(menuBar, XtNchildren, &children,
			       XtNnumChildren, &num_children,
			       NULL);
	if (XtConvertAndStore(w ? w :
				(num_children > 0) ? children[0] : menuBar,
			      XtRString, &from,
#ifdef FONTSET_ALWAYS
			      XtRFontSet, &to
#else
			      XtRFontStruct, &to
#endif
		    ) == False)
	    return None;
	// "font" should now contain data
    }
    else
#ifdef FONTSET_ALWAYS
	font = (XFontSet)gui.menu_fontset;
#else
	font = (XFontStruct *)gui.menu_font;
#endif

    {
	int	    width, height;
	GC	    draw_gc, undraw_gc;
	XGCValues   gc_values;
	XPoint	    points[3];

#ifdef FONTSET_ALWAYS
	height = fontset_height2(font);
#else
	height = font->max_bounds.ascent + font->max_bounds.descent;
#endif
	width = height - 2;
	puller_width = width + 4;
	retval = XCreatePixmap(gui.dpy,DefaultRootWindow(gui.dpy),width,
			       height, 1);
	gc_values.foreground = 1;
	gc_values.background = 0;
	draw_gc = XCreateGC(gui.dpy, retval,
		    GCForeground | GCBackground,
		    &gc_values);
	gc_values.foreground = 0;
	gc_values.background = 1;
	undraw_gc = XCreateGC(gui.dpy, retval,
			      GCForeground | GCBackground,
			      &gc_values);
	points[0].x = 0;
	points[0].y = 0;
	points[1].x = width - 1;
	points[1].y = (height - 1) / 2;
	points[2].x = 0;
	points[2].y = height - 1;
	XFillRectangle(gui.dpy, retval, undraw_gc, 0, 0, height, height);
	XFillPolygon(gui.dpy, retval, draw_gc, points, XtNumber(points),
		     Convex, CoordModeOrigin);
	XFreeGC(gui.dpy, draw_gc);
	XFreeGC(gui.dpy, undraw_gc);
    }
    return retval;
}
#endif

/*
 * Called when the GUI is not going to start after all.
 */
    void
gui_x11_destroy_widgets(void)
{
    textArea = NULL;
#ifdef FEAT_MENU
    menuBar = NULL;
#endif
#ifdef FEAT_TOOLBAR
    toolBar = NULL;
#endif
}

#if defined(FEAT_TOOLBAR) || defined(PROTO)
# include "gui_x11_pm.h"
# ifdef HAVE_X11_XPM_H
#  include <X11/xpm.h>
# endif

static void createXpmImages(char_u *path, char **xpm, Pixmap *sen);

/*
 * Allocated a pixmap for toolbar menu "menu".
 * Return in "sen".
 */
    static void
get_toolbar_pixmap(vimmenu_T *menu, Pixmap *sen)
{
    char_u	buf[MAXPATHL];		// buffer storing expanded pathname
    char	**xpm = NULL;		// xpm array

    buf[0] = NUL;			// start with NULL path

    if (menu->iconfile != NULL)
    {
	// Use the "icon="  argument.
	gui_find_iconfile(menu->iconfile, buf, "xpm");
	createXpmImages(buf, NULL, sen);

	// If it failed, try using the menu name.
	if (*sen == (Pixmap)0 && gui_find_bitmap(menu->name, buf, "xpm") == OK)
	    createXpmImages(buf, NULL, sen);
	if (*sen != (Pixmap)0)
	    return;
    }

    if (menu->icon_builtin || gui_find_bitmap(menu->name, buf, "xpm") == FAIL)
    {
	if (menu->iconidx >= 0 && menu->iconidx
	      < (int)ARRAY_LENGTH(built_in_pixmaps))
	    xpm = built_in_pixmaps[menu->iconidx];
	else
	    xpm = tb_blank_xpm;
    }

    if (xpm != NULL || buf[0] != NUL)
	createXpmImages(buf, xpm, sen);
}

/*
 * Read an Xpm file, doing color substitutions for the foreground and
 * background colors. If there is an error reading a color xpm file,
 * drop back and read the monochrome file. If successful, create the
 * insensitive Pixmap too.
 */
    static void
createXpmImages(char_u *path, char **xpm, Pixmap *sen)
{
    Window	rootWindow;
    XpmAttributes attrs;
    XpmColorSymbol color[5] =
    {
	{"none", "none", 0},
	{"iconColor1", NULL, 0},
	{"bottomShadowColor", NULL, 0},
	{"topShadowColor", NULL, 0},
	{"selectColor", NULL, 0}
    };
    int		screenNum;
    int		status;
    Pixmap	mask;
    Pixmap	map;

    gui_mch_get_toolbar_colors(
	    &color[BACKGROUND].pixel,
	    &color[FOREGROUND].pixel,
	    &color[BOTTOM_SHADOW].pixel,
	    &color[TOP_SHADOW].pixel,
	    &color[HIGHLIGHT].pixel);

    // Setup the color substitution table
    attrs.valuemask = XpmColorSymbols;
    attrs.colorsymbols = color;
    attrs.numsymbols = 5;

    screenNum = DefaultScreen(gui.dpy);
    rootWindow = RootWindow(gui.dpy, screenNum);

    // Create the "sensitive" pixmap
    if (xpm != NULL)
	status = XpmCreatePixmapFromData(gui.dpy, rootWindow, xpm,
							 &map, &mask, &attrs);
    else
	status = XpmReadFileToPixmap(gui.dpy, rootWindow, (char *)path,
							 &map, &mask, &attrs);
    if (status == XpmSuccess && map != 0)
    {
	XGCValues   gcvalues;
	GC	    back_gc;
	GC	    mask_gc;

	// Need to create new Pixmaps with the mask applied.
	gcvalues.foreground = color[BACKGROUND].pixel;
	back_gc = XCreateGC(gui.dpy, map, GCForeground, &gcvalues);
	mask_gc = XCreateGC(gui.dpy, map, GCForeground, &gcvalues);
	XSetClipMask(gui.dpy, mask_gc, mask);

	// Create the "sensitive" pixmap.
	*sen = XCreatePixmap(gui.dpy, rootWindow,
		 attrs.width, attrs.height,
		 DefaultDepth(gui.dpy, screenNum));
	XFillRectangle(gui.dpy, *sen, back_gc, 0, 0,
		attrs.width, attrs.height);
	XCopyArea(gui.dpy, map, *sen, mask_gc, 0, 0,
		attrs.width, attrs.height, 0, 0);

	XFreeGC(gui.dpy, back_gc);
	XFreeGC(gui.dpy, mask_gc);
	XFreePixmap(gui.dpy, map);
    }
    else
	*sen = 0;

    XpmFreeAttributes(&attrs);
}

    void
gui_mch_set_toolbar_pos(
    int	    x,
    int	    y,
    int	    w,
    int	    h)
{
    Dimension	border;
    int		height;

    if (!XtIsManaged(toolBar))	// nothing to do
	return;
    XtUnmanageChild(toolBar);
    XtVaGetValues(toolBar,
		XtNborderWidth, &border,
		NULL);
    height = h - 2 * border;
    if (height < 0)
	height = 1;
    XtVaSetValues(toolBar,
		  XtNhorizDistance, x,
		  XtNvertDistance, y,
		  XtNwidth, w - 2 * border,
		  XtNheight,	height,
		  NULL);
    XtManageChild(toolBar);
}
#endif

    void
gui_mch_set_text_area_pos(
    int	    x,
    int	    y,
    int	    w,
    int	    h)
{
    XtUnmanageChild(textArea);
    XtVaSetValues(textArea,
		  XtNhorizDistance, x,
		  XtNvertDistance, y,
		  XtNwidth, w,
		  XtNheight, h,
		  NULL);
    XtManageChild(textArea);
#ifdef FEAT_TOOLBAR
    // Give keyboard focus to the textArea instead of the toolbar.
    gui_mch_reset_focus();
#endif
}

#ifdef FEAT_TOOLBAR
/*
 * A toolbar button has been pushed; now reset the input focus
 * such that the user can type page up/down etc. and have the
 * input go to the editor window, not the button
 */
    static void
gui_mch_reset_focus(void)
{
    XtSetKeyboardFocus(vimForm, textArea);
}
#endif


    void
gui_x11_set_back_color(void)
{
    if (textArea != NULL)
	XtVaSetValues(textArea,
		  XtNbackground, gui.back_pixel,
		  NULL);
}

#if defined(FEAT_MENU) || defined(PROTO)
/*
 * Menu stuff.
 */

static char_u	*make_pull_name(char_u * name);
static Widget	get_popup_entry(Widget w);
static Widget	submenu_widget(Widget);
static Boolean	has_submenu(Widget);
static void gui_mch_submenu_change(vimmenu_T *mp, int colors);
static void gui_athena_menu_font(Widget id);

    void
gui_mch_enable_menu(int flag)
{
    if (flag)
    {
	XtManageChild(menuBar);
# ifdef FEAT_TOOLBAR
	if (XtIsManaged(toolBar))
	{
	    XtVaSetValues(toolBar,
		XtNvertDistance,    gui.menu_height,
		NULL);
	    XtVaSetValues(textArea,
		XtNvertDistance,    gui.menu_height + gui.toolbar_height,
		NULL);
	}
# endif
    }
    else
    {
	XtUnmanageChild(menuBar);
# ifdef FEAT_TOOLBAR
	if (XtIsManaged(toolBar))
	{
	    XtVaSetValues(toolBar,
		XtNvertDistance,    0,
		NULL);
	}
# endif
    }
}

    void
gui_mch_set_menu_pos(
    int	    x,
    int	    y,
    int	    w,
    int	    h)
{
    Dimension	border;
    int		height;

    XtUnmanageChild(menuBar);
    XtVaGetValues(menuBar, XtNborderWidth, &border, NULL);
    // avoid trouble when there are no menu items, and h is 1
    height = h - 2 * border;
    if (height < 0)
	height = 1;
    XtVaSetValues(menuBar,
		XtNhorizDistance, x,
		XtNvertDistance, y,
		XtNwidth, w - 2 * border,
		XtNheight, height,
		NULL);
    XtManageChild(menuBar);
}

/*
 * Used to calculate the insertion position of a widget with respect to its
 * neighbors.
 *
 * Valid range of return values is: 0 (beginning of children) to
 *				    numChildren (end of children).
 */
    static Cardinal
athena_calculate_ins_pos(Widget widget)
{
    // Assume that if the parent of the vimmenu_T is NULL, then we can get
    // to this menu by traversing "next", starting at "root_menu".
    //
    // This holds true for popup menus, toolbar, and toplevel menu items.

    // Popup menus:  "id" is NULL. Only submenu_id is valid

    // Menus that are not toplevel: "parent" will be non-NULL, "id" &
    // "submenu_id" will be non-NULL.

    // Toplevel menus: "parent" is NULL, id is the widget of the menu item

    WidgetList	children;
    Cardinal	num_children = 0;
    int		retval;
    Arg		args[2];
    int		n = 0;
    int		i;

    XtSetArg(args[n], XtNchildren, &children); n++;
    XtSetArg(args[n], XtNnumChildren, &num_children); n++;
    XtGetValues(XtParent(widget), args, n);

    retval = num_children;
    for (i = 0; i < (int)num_children; ++i)
    {
	Widget	current = children[i];
	vimmenu_T	*menu = NULL;

	for (menu = (a_cur_menu->parent == NULL)
			       ? root_menu : a_cur_menu->parent->children;
			       menu != NULL;
			       menu = menu->next)
	    if (current == menu->id
		    && a_cur_menu->priority < menu->priority
		    && i < retval)
		retval = i;
    }
    return retval;
}

    void
gui_mch_add_menu(vimmenu_T *menu, int idx UNUSED)
{
    char_u	*pullright_name;
    Dimension	height, space, border;
    vimmenu_T	*parent = menu->parent;

    a_cur_menu = menu;
    if (parent == NULL)
    {
	if (menu_is_popup(menu->dname))
	{
	    menu->submenu_id = XtVaCreatePopupShell((char *)menu->dname,
		simpleMenuWidgetClass,	vimShell,
		XtNinsertPosition,	athena_calculate_ins_pos,
		XtNtranslations,	popupTrans,
		NULL);
	    gui_athena_menu_colors(menu->submenu_id);
	}
	else if (menu_is_menubar(menu->dname))
	{
	    menu->id = XtVaCreateManagedWidget((char *)menu->dname,
		menuButtonWidgetClass, menuBar,
		XtNmenuName,	    menu->dname,
#ifdef FONTSET_ALWAYS
		XtNinternational,   True,
#endif
		NULL);
	    if (menu->id == (Widget)0)
		return;
	    gui_athena_menu_colors(menu->id);
	    gui_athena_menu_font(menu->id);

	    menu->submenu_id = XtVaCreatePopupShell((char *)menu->dname,
		simpleMenuWidgetClass, menu->id,
		XtNinsertPosition,	athena_calculate_ins_pos,
		XtNtranslations,	supermenuTrans,
		NULL);
	    gui_athena_menu_colors(menu->submenu_id);
	    gui_athena_menu_font(menu->submenu_id);

	    // Don't update the menu height when it was set at a fixed value
	    if (!gui.menu_height_fixed)
	    {
		/*
		 * When we add a top-level item to the menu bar, we can figure
		 * out how high the menu bar should be.
		 */
		XtVaGetValues(menuBar,
			XtNvSpace,	&space,
			XtNborderWidth, &border,
			NULL);
		XtVaGetValues(menu->id,
			XtNheight,	&height,
			NULL);
		gui.menu_height = height + 2 * (space + border);
	    }
	}
    }
    else if (parent->submenu_id != (Widget)0)
    {
	menu->id = XtVaCreateManagedWidget((char *)menu->dname,
	    smeBSBObjectClass, parent->submenu_id,
	    XtNlabel, menu->dname,
#ifdef FONTSET_ALWAYS
	    XtNinternational,	True,
#endif
	    NULL);
	if (menu->id == (Widget)0)
	    return;
	if (pullerBitmap == None)
	    pullerBitmap = gui_athena_create_pullright_pixmap(menu->id);

	XtVaSetValues(menu->id, XtNrightBitmap, pullerBitmap,
				NULL);
	// If there are other menu items that are not pulldown menus,
	// we need to adjust the right margins of those, too.
	{
	    WidgetList	children;
	    Cardinal	num_children;
	    int		i;

	    XtVaGetValues(parent->submenu_id, XtNchildren, &children,
					      XtNnumChildren, &num_children,
					      NULL);
	    for (i = 0; i < (int)num_children; ++i)
	    {
		XtVaSetValues(children[i],
			      XtNrightMargin, puller_width,
			      NULL);
	    }
	}
	gui_athena_menu_colors(menu->id);
	gui_athena_menu_font(menu->id);

	pullright_name = make_pull_name(menu->dname);
	menu->submenu_id = XtVaCreatePopupShell((char *)pullright_name,
	    simpleMenuWidgetClass, parent->submenu_id,
	    XtNtranslations, menuTrans,
	    NULL);
	gui_athena_menu_colors(menu->submenu_id);
	gui_athena_menu_font(menu->submenu_id);
	vim_free(pullright_name);
	XtAddCallback(menu->submenu_id, XtNpopupCallback,
		      gui_athena_popup_callback, (XtPointer)menu);

	if (parent->parent != NULL)
	    XtOverrideTranslations(parent->submenu_id, parentTrans);
    }
    a_cur_menu = NULL;
}

// Used to determine whether a SimpleMenu has pulldown entries.
//
// "id" is the parent of the menu items.
// Ignore widget "ignore" in the pane.
    static Boolean
gui_athena_menu_has_submenus(Widget id, Widget ignore)
{
    WidgetList	children;
    Cardinal	num_children;
    int		i;

    XtVaGetValues(id, XtNchildren, &children,
		      XtNnumChildren, &num_children,
		      NULL);
    for (i = 0; i < (int)num_children; ++i)
    {
	if (children[i] == ignore)
	    continue;
	if (has_submenu(children[i]))
	    return True;
    }
    return False;
}

    static void
gui_athena_menu_font(Widget id)
{
#ifdef FONTSET_ALWAYS
    if (gui.menu_fontset != NOFONTSET)
    {
	if (XtIsManaged(id))
	{
	    XtUnmanageChild(id);
	    XtVaSetValues(id, XtNfontSet, gui.menu_fontset, NULL);
	    // We should force the widget to recalculate its
	    // geometry now.
	    XtManageChild(id);
	}
	else
	    XtVaSetValues(id, XtNfontSet, gui.menu_fontset, NULL);
	if (has_submenu(id))
	    XtVaSetValues(id, XtNrightBitmap, pullerBitmap, NULL);
    }
#else
    int		managed = FALSE;

    if (gui.menu_font != NOFONT)
    {
	if (XtIsManaged(id))
	{
	    XtUnmanageChild(id);
	    managed = TRUE;
	}

# ifdef FEAT_XFONTSET
	if (gui.fontset != NOFONTSET)
	    XtVaSetValues(id, XtNfontSet, gui.menu_font, NULL);
	else
# endif
	    XtVaSetValues(id, XtNfont, gui.menu_font, NULL);
	if (has_submenu(id))
	    XtVaSetValues(id, XtNrightBitmap, pullerBitmap, NULL);

	// Force the widget to recalculate its geometry now.
	if (managed)
	    XtManageChild(id);
    }
#endif
}


    void
gui_mch_new_menu_font(void)
{
    Pixmap oldpuller = None;

    if (menuBar == (Widget)0)
	return;

    if (pullerBitmap != None)
    {
	oldpuller = pullerBitmap;
	pullerBitmap = gui_athena_create_pullright_pixmap(NULL);
    }
    gui_mch_submenu_change(root_menu, FALSE);

    {
	// Iterate through the menubar menu items and get the height of
	// each one.  The menu bar height is set to the maximum of all
	// the heights.
	vimmenu_T *mp;
	int max_height = 9999;

	FOR_ALL_MENUS(mp)
	{
	    if (menu_is_menubar(mp->dname))
	    {
		Dimension height;

		XtVaGetValues(mp->id,
			XtNheight, &height,
			NULL);
		if (height < max_height)
		    max_height = height;
	    }
	}
	if (max_height != 9999)
	{
	    // Don't update the menu height when it was set at a fixed value
	    if (!gui.menu_height_fixed)
	    {
		Dimension   space, border;

		XtVaGetValues(menuBar,
			XtNvSpace,	&space,
			XtNborderWidth, &border,
			NULL);
		gui.menu_height = max_height + 2 * (space + border);
	    }
	}
    }
    // Now, to simulate the window being resized.  Only, this
    // will resize the window to its current state.
    //
    // There has to be a better way, but I do not see one at this time.
    // (David Harrison)
    {
	Position w, h;

	XtVaGetValues(vimShell,
		XtNwidth, &w,
		XtNheight, &h,
		NULL);
	gui_resize_shell(w, h
#ifdef FEAT_XIM
						- xim_get_status_area_height()
#endif
		     );
    }
    gui_set_shellsize(FALSE, TRUE, RESIZE_VERT);
    ui_new_shellsize();
    if (oldpuller != None)
	XFreePixmap(gui.dpy, oldpuller);
}

#if defined(FEAT_BEVAL_GUI) || defined(PROTO)
    void
gui_mch_new_tooltip_font(void)
{
#  ifdef FEAT_TOOLBAR
    vimmenu_T   *menu;

    if (toolBar == (Widget)0)
	return;

    menu = gui_find_menu((char_u *)"ToolBar");
    if (menu != NULL)
	gui_mch_submenu_change(menu, FALSE);
#  endif
}

    void
gui_mch_new_tooltip_colors(void)
{
# ifdef FEAT_TOOLBAR
    vimmenu_T   *menu;

    if (toolBar == (Widget)0)
	return;

    menu = gui_find_menu((char_u *)"ToolBar");
    if (menu != NULL)
	gui_mch_submenu_change(menu, TRUE);
# endif
}
#endif

    static void
gui_mch_submenu_change(
    vimmenu_T	*menu,
    int		colors)		// TRUE for colors, FALSE for font
{
    vimmenu_T	*mp;

    for (mp = menu; mp != NULL; mp = mp->next)
    {
	if (mp->id != (Widget)0)
	{
	    if (colors)
	    {
		gui_athena_menu_colors(mp->id);
#ifdef FEAT_TOOLBAR
		// For a toolbar item: Free the pixmap and allocate a new one,
		// so that the background color is right.
		if (mp->image != (Pixmap)0)
		{
		    XFreePixmap(gui.dpy, mp->image);
		    get_toolbar_pixmap(mp, &mp->image);
		    if (mp->image != (Pixmap)0)
			XtVaSetValues(mp->id, XtNbitmap, mp->image, NULL);
		}

# ifdef FEAT_BEVAL_GUI
		// If we have a tooltip, then we need to change its colors
		if (mp->tip != NULL)
		{
		    Arg args[2];

		    args[0].name = XtNbackground;
		    args[0].value = gui.tooltip_bg_pixel;
		    args[1].name = XtNforeground;
		    args[1].value = gui.tooltip_fg_pixel;
		    XtSetValues(mp->tip->balloonLabel, &args[0], XtNumber(args));
		}
# endif
#endif
	    }
	    else
	    {
		gui_athena_menu_font(mp->id);
#ifdef FEAT_BEVAL_GUI
		// If we have a tooltip, then we need to change its font
		// Assume XtNinternational == True (in createBalloonEvalWindow)
		if (mp->tip != NULL)
		{
		    Arg args[1];

		    args[0].name = XtNfontSet;
		    args[0].value = (XtArgVal)gui.tooltip_fontset;
		    XtSetValues(mp->tip->balloonLabel, &args[0], XtNumber(args));
		}
#endif
	    }
	}

	if (mp->children != NULL)
	{
	    // Set the colors/font for the tear off widget
	    if (mp->submenu_id != (Widget)0)
	    {
		if (colors)
		    gui_athena_menu_colors(mp->submenu_id);
		else
		    gui_athena_menu_font(mp->submenu_id);
	    }
	    // Set the colors for the children
	    gui_mch_submenu_change(mp->children, colors);
	}
    }
}

/*
 * Make a submenu name into a pullright name.
 * Replace '.' by '_', can't include '.' in the submenu name.
 */
    static char_u *
make_pull_name(char_u * name)
{
    char_u  *pname;
    char_u  *p;

    pname = vim_strnsave(name, STRLEN(name) + strlen("-pullright"));
    if (pname != NULL)
    {
	strcat((char *)pname, "-pullright");
	while ((p = vim_strchr(pname, '.')) != NULL)
	    *p = '_';
    }
    return pname;
}

    void
gui_mch_add_menu_item(vimmenu_T *menu, int idx UNUSED)
{
    vimmenu_T	*parent = menu->parent;

    a_cur_menu = menu;
# ifdef FEAT_TOOLBAR
    if (menu_is_toolbar(parent->name))
    {
	WidgetClass	type;
	int		n;
	Arg		args[21];

	n = 0;
	if (menu_is_separator(menu->name))
	{
	    XtSetArg(args[n], XtNlabel, ""); n++;
	    XtSetArg(args[n], XtNborderWidth, 0); n++;
	}
	else
	{
	    get_toolbar_pixmap(menu, &menu->image);
	    XtSetArg(args[n], XtNlabel, menu->dname); n++;
	    XtSetArg(args[n], XtNinternalHeight, 1); n++;
	    XtSetArg(args[n], XtNinternalWidth, 1); n++;
	    XtSetArg(args[n], XtNborderWidth, 1); n++;
	    if (menu->image != 0)
		XtSetArg(args[n], XtNbitmap, menu->image); n++;
	}
	XtSetArg(args[n], XtNhighlightThickness, 0); n++;
	type = commandWidgetClass;
	// TODO: figure out the position in the toolbar?
	//       This currently works fine for the default toolbar, but
	//       what if we add/remove items during later runtime?

	// NOTE: "idx" isn't used here.  The position is calculated by
	//       athena_calculate_ins_pos().  The position it calculates
	//       should be equal to "idx".
	// TODO: Could we just store "idx" and use that as the child
	// placement?

	if (menu->id == NULL)
	{
	    menu->id = XtCreateManagedWidget((char *)menu->dname,
			type, toolBar, args, n);
	    XtAddCallback(menu->id,
		    XtNcallback, gui_x11_menu_cb, menu);
	}
	else
	    XtSetValues(menu->id, args, n);
	gui_athena_menu_colors(menu->id);

#ifdef FEAT_BEVAL_GUI
	gui_mch_menu_set_tip(menu);
#endif

	menu->parent = parent;
	menu->submenu_id = NULL;
	if (!XtIsManaged(toolBar)
		    && vim_strchr(p_go, GO_TOOLBAR) != NULL)
	    gui_mch_show_toolbar(TRUE);
	gui.toolbar_height = gui_mch_compute_toolbar_height();
	return;
    } // toolbar menu item
# endif

    // Add menu separator
    if (menu_is_separator(menu->name))
    {
	menu->submenu_id = (Widget)0;
	menu->id = XtVaCreateManagedWidget((char *)menu->dname,
		smeLineObjectClass, parent->submenu_id,
		NULL);
	if (menu->id == (Widget)0)
	    return;
	gui_athena_menu_colors(menu->id);
    }
    else
    {
	if (parent != NULL && parent->submenu_id != (Widget)0)
	{
	    menu->submenu_id = (Widget)0;
	    menu->id = XtVaCreateManagedWidget((char *)menu->dname,
		    smeBSBObjectClass, parent->submenu_id,
		    XtNlabel, menu->dname,
#ifdef FONTSET_ALWAYS
		    XtNinternational,	True,
#endif
		    NULL);
	    if (menu->id == (Widget)0)
		return;

	    // If there are other "pulldown" items in this pane, then adjust
	    // the right margin to accommodate the arrow pixmap, otherwise
	    // the right margin will be the same as the left margin.
	    {
		Dimension   left_margin;

		XtVaGetValues(menu->id, XtNleftMargin, &left_margin, NULL);
		XtVaSetValues(menu->id, XtNrightMargin,
			gui_athena_menu_has_submenus(parent->submenu_id, NULL) ?
			    puller_width :
			    left_margin,
			NULL);
	    }

	    gui_athena_menu_colors(menu->id);
	    gui_athena_menu_font(menu->id);
	    XtAddCallback(menu->id, XtNcallback, gui_x11_menu_cb,
		    (XtPointer)menu);
	}
    }
    a_cur_menu = NULL;
}

#if defined(FEAT_TOOLBAR) || defined(PROTO)
    void
gui_mch_show_toolbar(int showit)
{
    Cardinal	numChildren;	    // how many children toolBar has

    if (toolBar == (Widget)0)
	return;
    XtVaGetValues(toolBar, XtNnumChildren, &numChildren, NULL);
    if (showit && numChildren > 0)
    {
	// Assume that we want to show the toolbar if p_toolbar contains valid
	// option settings, therefore p_toolbar must not be NULL.
	WidgetList  children;

	XtVaGetValues(toolBar, XtNchildren, &children, NULL);
	{
	    void    (*action)(BalloonEval *);
	    int	    text = 0;

	    if (strstr((const char *)p_toolbar, "tooltips"))
		action = &gui_mch_enable_beval_area;
	    else
		action = &gui_mch_disable_beval_area;
	    if (strstr((const char *)p_toolbar, "text"))
		text = 1;
	    else if (strstr((const char *)p_toolbar, "icons"))
		text = -1;
	    if (text != 0)
	    {
		vimmenu_T   *toolbar;
		vimmenu_T   *cur;

		FOR_ALL_MENUS(toolbar)
		    if (menu_is_toolbar(toolbar->dname))
			break;
		// Assumption: toolbar is NULL if there is no toolbar,
		//	       otherwise it contains the toolbar menu structure.
		//
		// Assumption: "numChildren" == the number of items in the list
		//	       of items beginning with toolbar->children.
		if (toolbar)
		{
		    for (cur = toolbar->children; cur; cur = cur->next)
		    {
			Arg	    args[2];
			int	    n = 0;

			// Enable/Disable tooltip (OK to enable while currently
			// enabled)
			if (cur->tip != NULL)
			    (*action)(cur->tip);
			if (text == 1)
			{
			    XtSetArg(args[n], XtNbitmap, None);
			    n++;
			    XtSetArg(args[n], XtNlabel,
				    menu_is_separator(cur->name) ? "" :
					(char *)cur->dname);
			    n++;
			}
			else
			{
			    XtSetArg(args[n], XtNbitmap, cur->image);
			    n++;
			    XtSetArg(args[n], XtNlabel, (cur->image == None) ?
				    menu_is_separator(cur->name) ?
					"" :
					(char *)cur->dname
				    :
				    (char *)None);
			    n++;
			}
			if (cur->id != NULL)
			{
			    XtUnmanageChild(cur->id);
			    XtSetValues(cur->id, args, n);
			    XtManageChild(cur->id);
			}
		    }
		}
	    }
	}
	gui.toolbar_height = gui_mch_compute_toolbar_height();
	XtManageChild(toolBar);
	if (XtIsManaged(menuBar))
	{
	    XtVaSetValues(textArea,
		    XtNvertDistance,    gui.toolbar_height + gui.menu_height,
		    NULL);
	    XtVaSetValues(toolBar,
		    XtNvertDistance,    gui.menu_height,
		    NULL);
	}
	else
	{
	    XtVaSetValues(textArea,
		    XtNvertDistance,    gui.toolbar_height,
		    NULL);
	    XtVaSetValues(toolBar,
		    XtNvertDistance,    0,
		    NULL);
	}
    }
    else
    {
	gui.toolbar_height = 0;
	if (XtIsManaged(menuBar))
	    XtVaSetValues(textArea,
		XtNvertDistance,    gui.menu_height,
		NULL);
	else
	    XtVaSetValues(textArea,
		XtNvertDistance,    0,
		NULL);

	XtUnmanageChild(toolBar);
    }
    gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
}


    int
gui_mch_compute_toolbar_height(void)
{
    Dimension	height;		    // total Toolbar height
    Dimension	whgt;		    // height of each widget
    Dimension	marginHeight;	    // XmNmarginHeight of toolBar
    Dimension	shadowThickness;    // thickness of Xtparent(toolBar)
    WidgetList	children;	    // list of toolBar's children
    Cardinal	numChildren;	    // how many children toolBar has
    int		i;

    height = 0;
    shadowThickness = 0;
    marginHeight = 0;
    if (toolBar != (Widget)0)
    {
	XtVaGetValues(toolBar,
		XtNborderWidth,	    &shadowThickness,
		XtNvSpace,	    &marginHeight,
		XtNchildren,	    &children,
		XtNnumChildren,	    &numChildren,
		NULL);
	for (i = 0; i < (int)numChildren; i++)
	{
	    whgt = 0;

	    XtVaGetValues(children[i], XtNheight, &whgt, NULL);
	    if (height < whgt)
		height = whgt;
	}
    }

    return (int)(height + (marginHeight << 1) + (shadowThickness << 1));
}

    void
gui_mch_get_toolbar_colors(
    Pixel	*bgp,
    Pixel	*fgp,
    Pixel       *bsp,
    Pixel	*tsp,
    Pixel	*hsp)
{
    XtVaGetValues(toolBar, XtNbackground, bgp, XtNborderColor, fgp, NULL);
    *bsp = *bgp;
    *tsp = *fgp;
    *hsp = *tsp;
}
#endif


    void
gui_mch_toggle_tearoffs(int enable UNUSED)
{
    // no tearoff menus
}

    void
gui_mch_new_menu_colors(void)
{
    if (menuBar == (Widget)0)
	return;
    if (gui.menu_fg_pixel != INVALCOLOR)
	XtVaSetValues(menuBar, XtNborderColor,	gui.menu_fg_pixel, NULL);
    gui_athena_menu_colors(menuBar);
#ifdef FEAT_TOOLBAR
    gui_athena_menu_colors(toolBar);
#endif

    gui_mch_submenu_change(root_menu, TRUE);
}

/*
 * Destroy the machine specific menu widget.
 */
    void
gui_mch_destroy_menu(vimmenu_T *menu)
{
    Widget	parent;

    // There is no item for the toolbar.
    if (menu->id == (Widget)0)
	return;

    parent = XtParent(menu->id);

    // When removing the last "pulldown" menu item from a pane, adjust the
    // right margins of the remaining widgets.
    if (menu->submenu_id != (Widget)0)
    {
	// Go through the menu items in the parent of this item and
	// adjust their margins, if necessary.
	// This takes care of the case when we delete the last menu item in a
	// pane that has a submenu.  In this case, there will be no arrow
	// pixmaps shown anymore.
	{
	    WidgetList  children;
	    Cardinal    num_children;
	    int		i;
	    Dimension	right_margin = 0;
	    Boolean	get_left_margin = False;

	    XtVaGetValues(parent, XtNchildren, &children,
				  XtNnumChildren, &num_children,
				  NULL);
	    if (gui_athena_menu_has_submenus(parent, menu->id))
		right_margin = puller_width;
	    else
		get_left_margin = True;

	    for (i = 0; i < (int)num_children; ++i)
	    {
		if (children[i] == menu->id)
		    continue;
		if (get_left_margin == True)
		{
		    Dimension left_margin;

		    XtVaGetValues(children[i], XtNleftMargin, &left_margin,
				  NULL);
		    XtVaSetValues(children[i], XtNrightMargin, left_margin,
				  NULL);
		}
		else
		    XtVaSetValues(children[i], XtNrightMargin, right_margin,
				  NULL);
	    }
	}
    }
    // Please be sure to destroy the parent widget first (i.e. menu->id).
    //
    // This code should be basically identical to that in the file gui_motif.c
    // because they are both Xt based.
    if (menu->id != (Widget)0)
    {
	Cardinal    num_children;
	Dimension   height, space, border;

	XtVaGetValues(menuBar,
		XtNvSpace,	&space,
		XtNborderWidth, &border,
		NULL);
	XtVaGetValues(menu->id,
		XtNheight,	&height,
		NULL);
#if defined(FEAT_TOOLBAR) && defined(FEAT_BEVAL_GUI)
	if (parent == toolBar && menu->tip != NULL)
	{
	    // We try to destroy this before the actual menu, because there are
	    // callbacks, etc. that will be unregistered during the tooltip
	    // destruction.
	    //
	    // If you call "gui_mch_destroy_beval_area()" after destroying
	    // menu->id, then the tooltip's window will have already been
	    // deallocated by Xt, and unknown behaviour will ensue (probably
	    // a core dump).
	    gui_mch_destroy_beval_area(menu->tip);
	    menu->tip = NULL;
	}
#endif
	/*
	 * This is a hack to stop the Athena simpleMenuWidget from getting a
	 * BadValue error when a menu's last child is destroyed. We check to
	 * see if this is the last child and if so, don't delete it. The parent
	 * will be deleted soon anyway, and it will delete its children like
	 * all good widgets do.
	 */
	// NOTE: The cause of the BadValue X Protocol Error is because when the
	// last child is destroyed, it is first unmanaged, thus causing a
	// geometry resize request from the parent Shell widget.
	// Since the Shell widget has no more children, it is resized to have
	// width/height of 0.  XConfigureWindow() is then called with the
	// width/height of 0, which generates the BadValue.
	//
	// This happens in phase two of the widget destruction process.
	{
	    if (parent != menuBar
#ifdef FEAT_TOOLBAR
		    && parent != toolBar
#endif
		    )
	    {
		XtVaGetValues(parent, XtNnumChildren, &num_children, NULL);
		if (num_children > 1)
		    XtDestroyWidget(menu->id);
	    }
	    else
		XtDestroyWidget(menu->id);
	    menu->id = (Widget)0;
	}

	if (parent == menuBar)
	{
	    if (!gui.menu_height_fixed)
		gui.menu_height = height + 2 * (space + border);
	}
#ifdef FEAT_TOOLBAR
	else if (parent == toolBar)
	{
	    // When removing last toolbar item, don't display the toolbar.
	    XtVaGetValues(toolBar, XtNnumChildren, &num_children, NULL);
	    if (num_children == 0)
		gui_mch_show_toolbar(FALSE);
	    else
		gui.toolbar_height = gui_mch_compute_toolbar_height();
	}
#endif
    }
    if (menu->submenu_id != (Widget)0)
    {
	XtDestroyWidget(menu->submenu_id);
	menu->submenu_id = (Widget)0;
    }
}

    static void
gui_athena_menu_timeout(
    XtPointer	    client_data,
    XtIntervalId    *id UNUSED)
{
    Widget  w = (Widget)client_data;
    Widget  popup;

    timer = 0;
    if (XtIsSubclass(w,smeBSBObjectClass))
    {
	Pixmap p;

	XtVaGetValues(w, XtNrightBitmap, &p, NULL);
	if ((p != None) && (p != XtUnspecifiedPixmap))
	{
	    // We are dealing with an item that has a submenu
	    popup = get_popup_entry(XtParent(w));
	    if (popup == (Widget)0)
		return;
	    XtPopup(popup, XtGrabNonexclusive);
	}
    }
}

/*
 * This routine is used to calculate the position (in screen coordinates)
 * where a submenu should appear relative to the menu entry that popped it
 * up.  It should appear even with and just slightly to the left of the
 * rightmost end of the menu entry that caused the popup.
 *
 * This is called when XtPopup() is called.
 */
    static void
gui_athena_popup_callback(
    Widget	w,
    XtPointer	client_data,
    XtPointer	call_data UNUSED)
{
    // Assumption: XtIsSubclass(XtParent(w),simpleMenuWidgetClass)
    vimmenu_T	*menu = (vimmenu_T *)client_data;
    Dimension	width;
    Position	root_x, root_y;

    // First, popdown any siblings that may have menus popped up
    {
	vimmenu_T *i;

	FOR_ALL_CHILD_MENUS(menu->parent, i)
	{
	    if (i->submenu_id != NULL && XtIsManaged(i->submenu_id))
		XtPopdown(i->submenu_id);
	}
    }
    XtVaGetValues(XtParent(w),
		  XtNwidth,   &width,
		  NULL);
    // Assumption: XawSimpleMenuGetActiveEntry(XtParent(w)) == menu->id
    // i.e. This IS the active entry
    XtTranslateCoords(menu->id,width - 5, 0, &root_x, &root_y);
    XtVaSetValues(w, XtNx, root_x,
		     XtNy, root_y,
		     NULL);
}

    static void
gui_athena_popdown_submenus_action(
    Widget	w,
    XEvent	*event,
    String	*args,
    Cardinal	*nargs)
{
    WidgetList	children;
    Cardinal	num_children;

    XtVaGetValues(w, XtNchildren, &children,
		     XtNnumChildren, &num_children,
		     NULL);
    for (; num_children > 0; --num_children)
    {
	Widget child = children[num_children - 1];

	if (has_submenu(child))
	{
	    Widget temp_w;

	    temp_w = submenu_widget(child);
	    gui_athena_popdown_submenus_action(temp_w,event,args,nargs);
	    XtPopdown(temp_w);
	}
    }
}

/*
 * Used to determine if the given widget has a submenu that can be popped up.
 */
    static Boolean
has_submenu(Widget widget)
{
    if ((widget != NULL) && XtIsSubclass(widget,smeBSBObjectClass))
    {
	Pixmap p;

	XtVaGetValues(widget, XtNrightBitmap, &p, NULL);
	if ((p != None) && (p != XtUnspecifiedPixmap))
	    return True;
    }
    return False;
}

    static void
gui_athena_delayed_arm_action(
    Widget	w,
    XEvent	*event,
    String	*args,
    Cardinal	*nargs)
{
    Dimension	width, height;

    if (event->type != MotionNotify)
	return;

    XtVaGetValues(w,
	XtNwidth,   &width,
	XtNheight,  &height,
	NULL);

    if (event->xmotion.x >= (int)width || event->xmotion.y >= (int)height)
	return;

    {
	static Widget	    previous_active_widget = NULL;
	Widget		    current;

	current = XawSimpleMenuGetActiveEntry(w);
	if (current != previous_active_widget)
	{
	    if (timer)
	    {
		// If the timeout hasn't been triggered, remove it
		XtRemoveTimeOut(timer);
	    }
	    gui_athena_popdown_submenus_action(w,event,args,nargs);
	    if (has_submenu(current))
	    {
		XtAppAddTimeOut(XtWidgetToApplicationContext(w), 600L,
				gui_athena_menu_timeout,
				(XtPointer)current);
	    }
	    previous_active_widget = current;
	}
    }
}

    static Widget
get_popup_entry(Widget w)
{
    Widget	menuw;

    // Get the active entry for the current menu
    if ((menuw = XawSimpleMenuGetActiveEntry(w)) == (Widget)0)
	return NULL;

    return submenu_widget(menuw);
}

/*
 * Given the widget that has been determined to have a submenu, return the
 * submenu widget that is to be popped up.
 */
    static Widget
submenu_widget(Widget widget)
{
    // Precondition: has_submenu(widget) == True
    //	    XtIsSubclass(XtParent(widget),simpleMenuWidgetClass) == True

    char_u	*pullright_name;
    Widget	popup;

    pullright_name = make_pull_name((char_u *)XtName(widget));
    popup = XtNameToWidget(XtParent(widget), (char *)pullright_name);
    vim_free(pullright_name);

    return popup;
    // Postcondition: (popup != NULL) implies
    // (XtIsSubclass(popup,simpleMenuWidgetClass) == True)
}

    void
gui_mch_show_popupmenu(vimmenu_T *menu)
{
    int		rootx, rooty, winx, winy;
    Window	root, child;
    unsigned int mask;

    if (menu->submenu_id == (Widget)0)
	return;

    // Position the popup menu at the pointer
    if (XQueryPointer(gui.dpy, XtWindow(vimShell), &root, &child,
		&rootx, &rooty, &winx, &winy, &mask))
    {
	rootx -= 30;
	if (rootx < 0)
	    rootx = 0;
	rooty -= 5;
	if (rooty < 0)
	    rooty = 0;
	XtVaSetValues(menu->submenu_id,
		XtNx, rootx,
		XtNy, rooty,
		NULL);
    }

    XtOverrideTranslations(menu->submenu_id, popupTrans);
    XtPopupSpringLoaded(menu->submenu_id);
}

#endif // FEAT_MENU

/*
 * Set the menu and scrollbar colors to their default values.
 */
    void
gui_mch_def_colors(void)
{
    /*
     * Get the colors ourselves.  Using the automatic conversion doesn't
     * handle looking for approximate colors.
     */
    if (gui.in_use)
    {
	gui.menu_fg_pixel = gui_get_color((char_u *)gui.rsrc_menu_fg_name);
	gui.menu_bg_pixel = gui_get_color((char_u *)gui.rsrc_menu_bg_name);
	gui.scroll_fg_pixel = gui_get_color((char_u *)gui.rsrc_scroll_fg_name);
	gui.scroll_bg_pixel = gui_get_color((char_u *)gui.rsrc_scroll_bg_name);
#ifdef FEAT_BEVAL_GUI
	gui.tooltip_fg_pixel = gui_get_color((char_u *)gui.rsrc_tooltip_fg_name);
	gui.tooltip_bg_pixel = gui_get_color((char_u *)gui.rsrc_tooltip_bg_name);
#endif
    }
}


/*
 * Scrollbar stuff.
 */

    void
gui_mch_set_scrollbar_thumb(
    scrollbar_T	*sb,
    long	val,
    long	size,
    long	max)
{
    double	v, s;

    if (sb->id == (Widget)0)
	return;

    /*
     * Athena scrollbar must go from 0.0 to 1.0.
     */
    if (max == 0)
    {
	// So you can't scroll it at all (normally it scrolls past end)
#ifdef FEAT_GUI_NEXTAW
	XawScrollbarSetThumb(sb->id, 0.0, 1.0);
#else
	vim_XawScrollbarSetThumb(sb->id, 0.0, 1.0, 0.0);
#endif
    }
    else
    {
	v = (double)val / (double)(max + 1);
	s = (double)size / (double)(max + 1);
#ifdef FEAT_GUI_NEXTAW
	XawScrollbarSetThumb(sb->id, v, s);
#else
	vim_XawScrollbarSetThumb(sb->id, v, s, 1.0);
#endif
    }
}

    void
gui_mch_set_scrollbar_pos(
    scrollbar_T *sb,
    int		x,
    int		y,
    int		w,
    int		h)
{
    if (sb->id == (Widget)0)
	return;

    XtUnmanageChild(sb->id);
    XtVaSetValues(sb->id,
		  XtNhorizDistance, x,
		  XtNvertDistance, y,
		  XtNwidth, w,
		  XtNheight, h,
		  NULL);
    XtManageChild(sb->id);
}

    int
gui_mch_get_scrollbar_xpadding(void)
{
    // TODO: Calculate the padding for adjust scrollbar position when the
    // Window is maximized.
    return 0;
}

    int
gui_mch_get_scrollbar_ypadding(void)
{
    // TODO: Calculate the padding for adjust scrollbar position when the
    // Window is maximized.
    return 0;
}

    void
gui_mch_enable_scrollbar(scrollbar_T *sb, int flag)
{
    if (sb->id != (Widget)0)
    {
	if (flag)
	    XtManageChild(sb->id);
	else
	    XtUnmanageChild(sb->id);
    }
}

    void
gui_mch_create_scrollbar(
    scrollbar_T *sb,
    int		orient)	// SBAR_VERT or SBAR_HORIZ
{
    sb->id = XtVaCreateWidget("scrollBar",
#ifdef FEAT_GUI_NEXTAW
	    scrollbarWidgetClass, vimForm,
#else
	    vim_scrollbarWidgetClass, vimForm,
#endif
	    XtNresizable,   True,
	    XtNtop,	    XtChainTop,
	    XtNbottom,	    XtChainTop,
	    XtNleft,	    XtChainLeft,
	    XtNright,	    XtChainLeft,
	    XtNborderWidth, 0,
	    XtNorientation, (orient == SBAR_VERT) ? XtorientVertical
						  : XtorientHorizontal,
	    XtNforeground, gui.scroll_fg_pixel,
	    XtNbackground, gui.scroll_bg_pixel,
	    NULL);
    if (sb->id == (Widget)0)
	return;

    XtAddCallback(sb->id, XtNjumpProc,
		  gui_athena_scroll_cb_jump, (XtPointer)sb->ident);
    XtAddCallback(sb->id, XtNscrollProc,
		  gui_athena_scroll_cb_scroll, (XtPointer)sb->ident);

#ifdef FEAT_GUI_NEXTAW
    XawScrollbarSetThumb(sb->id, 0.0, 1.0);
#else
    vim_XawScrollbarSetThumb(sb->id, 0.0, 1.0, 0.0);
#endif
}

    void
gui_mch_destroy_scrollbar(scrollbar_T *sb)
{
    if (sb->id != (Widget)0)
	XtDestroyWidget(sb->id);
}

    void
gui_mch_set_scrollbar_colors(scrollbar_T *sb)
{
    if (sb->id != (Widget)0)
	XtVaSetValues(sb->id,
	    XtNforeground, gui.scroll_fg_pixel,
	    XtNbackground, gui.scroll_bg_pixel,
	    NULL);

    // This is needed for the rectangle below the vertical scrollbars.
    if (sb == &gui.bottom_sbar && vimForm != (Widget)0)
	gui_athena_scroll_colors(vimForm);
}

/*
 * Miscellaneous stuff:
 */
    Window
gui_x11_get_wid(void)
{
    return XtWindow(textArea);
}

#if defined(FEAT_BROWSE) || defined(PROTO)
/*
 * Put up a file requester.
 * Returns the selected name in allocated memory, or NULL for Cancel.
 */
    char_u *
gui_mch_browse(
    int		saving UNUSED,	// select file to write
    char_u	*title,		// title for the window
    char_u	*dflt,		// default name
    char_u	*ext UNUSED,	// extension added
    char_u	*initdir,	// initial directory, NULL for current dir
    char_u	*filter UNUSED)	// file name filter
{
    Position x, y;
    char_u	dirbuf[MAXPATHL];

    // Concatenate "initdir" and "dflt".
    if (initdir == NULL || *initdir == NUL)
	mch_dirname(dirbuf, MAXPATHL);
    else if (STRLEN(initdir) + 2 < MAXPATHL)
	STRCPY(dirbuf, initdir);
    else
	dirbuf[0] = NUL;
    if (dflt != NULL && *dflt != NUL
			      && STRLEN(dirbuf) + 2 + STRLEN(dflt) < MAXPATHL)
    {
	add_pathsep(dirbuf);
	STRCAT(dirbuf, dflt);
    }

    // Position the file selector just below the menubar
    XtTranslateCoords(vimShell, (Position)0, (Position)
#ifdef FEAT_MENU
	    gui.menu_height
#else
	    0
#endif
	    , &x, &y);
    return (char_u *)vim_SelFile(vimShell, (char *)title, (char *)dirbuf,
		  NULL, (int)x, (int)y, gui.menu_fg_pixel, gui.menu_bg_pixel,
		  gui.scroll_fg_pixel, gui.scroll_bg_pixel);
}
#endif

#if defined(FEAT_GUI_DIALOG) || defined(PROTO)

static int	dialogStatus;
static Atom	dialogatom;

/*
 * Callback function for the textfield.  When CR is hit this works like
 * hitting the "OK" button, ESC like "Cancel".
 */
    static void
keyhit_callback(
    Widget		w UNUSED,
    XtPointer		client_data UNUSED,
    XEvent		*event,
    Boolean		*cont UNUSED)
{
    char	buf[2];

    if (XLookupString(&(event->xkey), buf, 2, NULL, NULL) == 1)
    {
	if (*buf == CAR)
	    dialogStatus = 1;
	else if (*buf == ESC)
	    dialogStatus = 0;
    }
}

    static void
butproc(
    Widget	w UNUSED,
    XtPointer	client_data,
    XtPointer	call_data UNUSED)
{
    dialogStatus = (int)(long)client_data + 1;
}

/*
 * Function called when dialog window closed.
 */
    static void
dialog_wm_handler(
    Widget	w UNUSED,
    XtPointer	client_data UNUSED,
    XEvent	*event,
    Boolean	*dum UNUSED)
{
    if (event->type == ClientMessage
	    && (Atom)((XClientMessageEvent *)event)->data.l[0] == dialogatom)
	dialogStatus = 0;
}

    int
gui_mch_dialog(
    int		type UNUSED,
    char_u	*title,
    char_u	*message,
    char_u	*buttons,
    int		dfltbutton UNUSED,
    char_u	*textfield,
    int		ex_cmd UNUSED)
{
    char_u		*buts;
    char_u		*p, *next;
    XtAppContext	app;
    XEvent		event;
    Position		wd, hd;
    Position		wv, hv;
    Position		x, y;
    Widget		dialog;
    Widget		dialogshell;
    Widget		dialogmessage;
    Widget		dialogtextfield = 0;
    Widget		dialogButton;
    Widget		prev_dialogButton = NULL;
    int			butcount;
    int			vertical;

    if (title == NULL)
	title = (char_u *)_("Vim dialog");
    dialogStatus = -1;

    // if our pointer is currently hidden, then we should show it.
    gui_mch_mousehide(FALSE);

    // Check 'v' flag in 'guioptions': vertical button placement.
    vertical = (vim_strchr(p_go, GO_VERTICAL) != NULL);

    // The shell is created each time, to make sure it is resized properly
    dialogshell = XtVaCreatePopupShell("dialogShell",
	    transientShellWidgetClass, vimShell,
	    XtNtitle, title,
	    NULL);
    if (dialogshell == (Widget)0)
	goto error;

    dialog = XtVaCreateManagedWidget("dialog",
	    formWidgetClass, dialogshell,
	    XtNdefaultDistance, 20,
	    NULL);
    if (dialog == (Widget)0)
	goto error;
    gui_athena_menu_colors(dialog);
    dialogmessage = XtVaCreateManagedWidget("dialogMessage",
	    labelWidgetClass, dialog,
	    XtNlabel, message,
	    XtNtop, XtChainTop,
	    XtNbottom, XtChainTop,
	    XtNleft, XtChainLeft,
	    XtNright, XtChainLeft,
	    XtNresizable, True,
	    XtNborderWidth, 0,
	    NULL);
    gui_athena_menu_colors(dialogmessage);

    if (textfield != NULL)
    {
	dialogtextfield = XtVaCreateManagedWidget("textfield",
		asciiTextWidgetClass, dialog,
		XtNwidth, 400,
		XtNtop, XtChainTop,
		XtNbottom, XtChainTop,
		XtNleft, XtChainLeft,
		XtNright, XtChainRight,
		XtNfromVert, dialogmessage,
		XtNresizable, True,
		XtNstring, textfield,
		XtNlength, IOSIZE,
		XtNuseStringInPlace, True,
		XtNeditType, XawtextEdit,
		XtNwrap, XawtextWrapNever,
		XtNresize, XawtextResizeHeight,
		NULL);
	XtManageChild(dialogtextfield);
	XtAddEventHandler(dialogtextfield, KeyPressMask, False,
			    (XtEventHandler)keyhit_callback, (XtPointer)NULL);
	XawTextSetInsertionPoint(dialogtextfield,
					  (XawTextPosition)STRLEN(textfield));
	XtSetKeyboardFocus(dialog, dialogtextfield);
    }

    // make a copy, so that we can insert NULs
    buts = vim_strsave(buttons);
    if (buts == NULL)
	return -1;

    p = buts;
    for (butcount = 0; *p; ++butcount)
    {
	for (next = p; *next; ++next)
	{
	    if (*next == DLG_HOTKEY_CHAR)
		STRMOVE(next, next + 1);
	    if (*next == DLG_BUTTON_SEP)
	    {
		*next++ = NUL;
		break;
	    }
	}
	dialogButton = XtVaCreateManagedWidget("button",
		commandWidgetClass, dialog,
		XtNlabel, p,
		XtNtop, XtChainBottom,
		XtNbottom, XtChainBottom,
		XtNleft, XtChainLeft,
		XtNright, XtChainLeft,
		XtNfromVert, textfield == NULL ? dialogmessage : dialogtextfield,
		XtNvertDistance, vertical ? 4 : 20,
		XtNresizable, False,
		NULL);
	gui_athena_menu_colors(dialogButton);
	if (butcount > 0)
	    XtVaSetValues(dialogButton,
		    vertical ? XtNfromVert : XtNfromHoriz, prev_dialogButton,
		    NULL);

	XtAddCallback(dialogButton, XtNcallback, butproc, (XtPointer)(long_u)butcount);
	p = next;
	prev_dialogButton = dialogButton;
    }
    vim_free(buts);

    XtRealizeWidget(dialogshell);

    // Setup for catching the close-window event, don't let it close Vim!
    dialogatom = XInternAtom(gui.dpy, "WM_DELETE_WINDOW", False);
    XSetWMProtocols(gui.dpy, XtWindow(dialogshell), &dialogatom, 1);
    XtAddEventHandler(dialogshell, NoEventMask, True, dialog_wm_handler, NULL);

    XtVaGetValues(dialogshell,
	    XtNwidth, &wd,
	    XtNheight, &hd,
	    NULL);
    XtVaGetValues(vimShell,
	    XtNwidth, &wv,
	    XtNheight, &hv,
	    NULL);
    XtTranslateCoords(vimShell,
	    (Position)((wv - wd) / 2),
	    (Position)((hv - hd) / 2),
	    &x, &y);
    if (x < 0)
	x = 0;
    if (y < 0)
	y = 0;
    XtVaSetValues(dialogshell, XtNx, x, XtNy, y, NULL);

    // Position the mouse pointer in the dialog, required for when focus
    // follows mouse.
    XWarpPointer(gui.dpy, (Window)0, XtWindow(dialogshell), 0, 0, 0, 0, 20, 40);


    app = XtWidgetToApplicationContext(dialogshell);

    XtPopup(dialogshell, XtGrabNonexclusive);

    for (;;)
    {
	XtAppNextEvent(app, &event);
	XtDispatchEvent(&event);
	if (dialogStatus >= 0)
	    break;
    }

    XtPopdown(dialogshell);

    if (textfield != NULL && dialogStatus < 0)
	*textfield = NUL;

error:
    XtDestroyWidget(dialogshell);

    return dialogStatus;
}
#endif

#if defined(FEAT_GUI_DIALOG) || defined(FEAT_MENU)
/*
 * Set the colors of Widget "id" to the menu colors.
 */
    static void
gui_athena_menu_colors(Widget id)
{
    if (gui.menu_bg_pixel != INVALCOLOR)
	XtVaSetValues(id, XtNbackground, gui.menu_bg_pixel, NULL);
    if (gui.menu_fg_pixel != INVALCOLOR)
	XtVaSetValues(id, XtNforeground, gui.menu_fg_pixel, NULL);
}
#endif

/*
 * Set the colors of Widget "id" to the scroll colors.
 */
    static void
gui_athena_scroll_colors(Widget id)
{
    if (gui.scroll_bg_pixel != INVALCOLOR)
	XtVaSetValues(id, XtNbackground, gui.scroll_bg_pixel, NULL);
    if (gui.scroll_fg_pixel != INVALCOLOR)
	XtVaSetValues(id, XtNforeground, gui.scroll_fg_pixel, NULL);
}