jdk/src/solaris/classes/sun/awt/X11/XBaseMenuWindow.java
author yan
Mon, 06 Oct 2008 16:45:00 +0400
changeset 1966 12a51fb0db0d
parent 439 3488710b02f8
child 3938 ef327bd847c0
permissions -rw-r--r--
5100701: Toolkit.getLockingKeyState() does not work on XToolkit, but works on Motif Summary: Does not work on Motif but works on XToolkit now; implemented using XQueryPointer. Reviewed-by: anthony

/*
 * Copyright 2005-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package sun.awt.X11;

import java.awt.*;
import java.awt.peer.*;
import java.awt.event.*;
import java.awt.image.ColorModel;

import sun.awt.*;

import java.util.ArrayList;
import java.util.Vector;
import java.util.logging.*;
import sun.java2d.SurfaceData;
import sun.java2d.SunGraphics2D;

/**
 * The abstract class XBaseMenuWindow is the superclass
 * of all menu windows.
 */
abstract public class XBaseMenuWindow extends XWindow {

    /************************************************
     *
     * Data members
     *
     ************************************************/

    private static Logger log = Logger.getLogger("sun.awt.X11.XBaseMenuWindow");

    /*
     * Colors are calculated using MotifColorUtilities class
     * from backgroundColor and are contained in these vars.
     */
    private Color backgroundColor;
    private Color foregroundColor;
    private Color lightShadowColor;
    private Color darkShadowColor;
    private Color selectedColor;
    private Color disabledColor;

    /**
     * Array of items.
     */
    private ArrayList<XMenuItemPeer> items;

    /**
     * Index of selected item in array of items
     */
    private int selectedIndex = -1;

    /**
     * Specifies currently showing submenu.
     */
    private XMenuPeer showingSubmenu = null;

    /**
     * Static synchronizational object.
     * Following operations should be synchronized
     * using this object:
     * 1. Access to items vector
     * 2. Access to selection
     * 3. Access to showing menu window member
     *
     * This is lowest level lock,
     * no other locks should be taken when
     * thread own this lock.
     */
    static private Object menuTreeLock = new Object();

    /************************************************
     *
     * Event processing
     *
     ************************************************/

    /**
     * If mouse button is clicked on item showing submenu
     * we have to hide its submenu.
     * And if mouse button is pressed on such item and
     * dragged to another, getShowingSubmenu() is changed.
     * So this member saves the item that the user
     * presses mouse button on _only_ if it's showing submenu.
     */
    private XMenuPeer showingMousePressedSubmenu = null;

    /**
     * If the PopupMenu is invoked as a result of right button click
     * first mouse event after grabInput would be MouseReleased.
     * We need to check if the user has moved mouse after input grab.
     * If yes - hide the PopupMenu. If no - do nothing
     */
    protected Point grabInputPoint = null;
    protected boolean hasPointerMoved = false;

    /************************************************
     *
     * Mapping data
     *
     ************************************************/

    /**
     * Mapping data that is filled in getMappedItems function
     * and reset in resetSize function. It contains array of
     * items in order that they appear on screen and may contain
     * additional data defined by descendants.
     */
    private MappingData mappingData;

    static class MappingData implements Cloneable {

        /**
         * Array of item in order that they appear on screen
         */
        private XMenuItemPeer[] items;

        /**
         * Constructs MappingData object with list
         * of menu items
         */
        MappingData(XMenuItemPeer[] items) {
            this.items = items;
        }

        /**
         * Constructs MappingData without items
         * This constructor should be used in case of errors
         */
        MappingData() {
            this.items = new XMenuItemPeer[0];
        }

        public Object clone() {
            try {
                return super.clone();
            } catch (CloneNotSupportedException ex) {
                throw new InternalError();
            }
        }

        public XMenuItemPeer[] getItems() {
            return this.items;
        }
    }

    /************************************************
     *
     * Construction
     *
     ************************************************/
    XBaseMenuWindow() {
        super(new XCreateWindowParams(new Object[] {
            DELAYED, Boolean.TRUE}));
    }

    /************************************************
     *
     * Abstract methods
     *
     ************************************************/

    /**
     * Returns parent menu window (not the X-heirarchy parent window)
     */
    protected abstract XBaseMenuWindow getParentMenuWindow();

    /**
     * Performs mapping of items in window.
     * This function creates and fills specific
     * descendant of MappingData
     * and sets mapping coordinates of items
     * This function should return default menu data
     * if errors occur
     */
    protected abstract MappingData map();

    /**
     * Calculates placement of submenu window
     * given bounds of item with submenu and
     * size of submenu window. Returns suggested
     * rectangle for submenu window in global coordinates
     * @param itemBounds the bounding rectangle of item
     * in local coordinates
     * @param windowSize the desired size of submenu's window
     */
    protected abstract Rectangle getSubmenuBounds(Rectangle itemBounds, Dimension windowSize);


    /**
     * This function is to be called if it's likely that size
     * of items was changed. It can be called from any thread
     * in any locked state, so it should not take locks
     */
    protected abstract void updateSize();

    /************************************************
     *
     * Initialization
     *
     ************************************************/

    /**
     * Overrides XBaseWindow.instantPreInit
     */
    void instantPreInit(XCreateWindowParams params) {
        super.instantPreInit(params);
        items = new ArrayList();
    }

    /************************************************
     *
     * General-purpose functions
     *
     ************************************************/

    /**
     * Returns static lock used for menus
     */
    static Object getMenuTreeLock() {
        return menuTreeLock;
    }

    /**
     * This function is called to clear all saved
     * size data.
     */
    protected void resetMapping() {
        mappingData = null;
    }

    /**
     * Invokes repaint procedure on eventHandlerThread
     */
    void postPaintEvent() {
        if (isShowing()) {
            PaintEvent pe = new PaintEvent(target, PaintEvent.PAINT,
                                           new Rectangle(0, 0, width, height));
            postEvent(pe);
        }
    }

    /************************************************
     *
     * Utility functions for manipulating items
     *
     ************************************************/

    /**
     * Thread-safely returns item at specified index
     * @param index the position of the item to be returned.
     */
    XMenuItemPeer getItem(int index) {
        if (index >= 0) {
            synchronized(getMenuTreeLock()) {
                if (items.size() > index) {
                    return items.get(index);
                }
            }
        }
        return null;
    }

    /**
     * Thread-safely creates a copy of the items vector
     */
    XMenuItemPeer[] copyItems() {
        synchronized(getMenuTreeLock()) {
            return (XMenuItemPeer[])items.toArray(new XMenuItemPeer[] {});
        }
    }


    /**
     * Thread-safely returns selected item
     */
    XMenuItemPeer getSelectedItem() {
        synchronized(getMenuTreeLock()) {
            if (selectedIndex >= 0) {
                if (items.size() > selectedIndex) {
                    return items.get(selectedIndex);
                }
            }
            return null;
        }
    }

    /**
     * Returns showing submenu, if any
     */
    XMenuPeer getShowingSubmenu() {
        synchronized(getMenuTreeLock()) {
            return showingSubmenu;
        }
    }

    /**
     * Adds item to end of items vector.
     * Note that this function does not perform
     * check for adding duplicate items
     * @param item item to add
     */
    public void addItem(MenuItem item) {
        XMenuItemPeer mp = (XMenuItemPeer)item.getPeer();
        if (mp != null) {
            mp.setContainer(this);
            synchronized(getMenuTreeLock()) {
                items.add(mp);
            }
        } else {
            if (log.isLoggable(Level.FINE)) {
                log.fine("WARNING: Attempt to add menu item without a peer");
            }
        }
        updateSize();
    }

    /**
     * Removes item at the specified index from items vector.
     * @param index the position of the item to be removed
     */
    public void delItem(int index) {
        synchronized(getMenuTreeLock()) {
            if (selectedIndex == index) {
                selectItem(null, false);
            } else if (selectedIndex > index) {
                selectedIndex--;
            }
            if (index < items.size()) {
                items.remove(index);
            } else {
                if (log.isLoggable(Level.FINE)) {
                    log.fine("WARNING: Attempt to remove non-existing menu item, index : " + index + ", item count : " + items.size());
                }
            }
        }
        updateSize();
    }

    /**
     * Clears items vector and loads specified vector
     * @param items vector to be loaded
     */
    public void reloadItems(Vector items) {
        synchronized(getMenuTreeLock()) {
            this.items.clear();
            MenuItem[] itemArray = (MenuItem[])items.toArray(new MenuItem[] {});
            int itemCnt = itemArray.length;
            for(int i = 0; i < itemCnt; i++) {
                addItem(itemArray[i]);
            }
        }
    }

    /**
     * Select specified item and shows/hides submenus if necessary
     * We can not select by index, so we need to select by ref.
     * @param item the item to be selected, null to clear selection
     * @param showWindowIfMenu if the item is XMenuPeer then its
     * window is shown/hidden according to this param.
     */
    void selectItem(XMenuItemPeer item, boolean showWindowIfMenu) {
        synchronized(getMenuTreeLock()) {
            XMenuPeer showingSubmenu = getShowingSubmenu();
            int newSelectedIndex = (item != null) ? items.indexOf(item) : -1;
            if (this.selectedIndex != newSelectedIndex) {
                if (log.isLoggable(Level.FINEST)) {
                    log.finest("Selected index changed, was : " + this.selectedIndex + ", new : " + newSelectedIndex);
                }
                this.selectedIndex = newSelectedIndex;
                postPaintEvent();
            }
            final XMenuPeer submenuToShow = (showWindowIfMenu && (item instanceof XMenuPeer)) ? (XMenuPeer)item : null;
            if (submenuToShow != showingSubmenu) {
                XToolkit.executeOnEventHandlerThread(target, new Runnable() {
                        public void run() {
                            doShowSubmenu(submenuToShow);
                        }
                    });
            }
        }
    }

    /**
     * Performs hiding of currently showing submenu
     * and showing of submenuToShow.
     * This function should be executed on eventHandlerThread
     * @param submenuToShow submenu to be shown or null
     * to hide currently showing submenu
     */
    private void doShowSubmenu(XMenuPeer submenuToShow) {
        XMenuWindow menuWindowToShow = (submenuToShow != null) ? submenuToShow.getMenuWindow() : null;
        Dimension dim = null;
        Rectangle bounds = null;
        //ensureCreated can invoke XWindowPeer.init() ->
        //XWindowPeer.initGraphicsConfiguration() ->
        //Window.getGraphicsConfiguration()
        //that tries to obtain Component.AWTTreeLock.
        //So it should be called outside awtLock()
        if (menuWindowToShow != null) {
            menuWindowToShow.ensureCreated();
        }
        XToolkit.awtLock();
        try {
            synchronized(getMenuTreeLock()) {
                if (showingSubmenu != submenuToShow) {
                    if (log.isLoggable(Level.FINER)) {
                        log.finest("Changing showing submenu");
                    }
                    if (showingSubmenu != null) {
                        XMenuWindow showingSubmenuWindow = showingSubmenu.getMenuWindow();
                        if (showingSubmenuWindow != null) {
                            showingSubmenuWindow.hide();
                        }
                    }
                    if (submenuToShow != null) {
                        dim = menuWindowToShow.getDesiredSize();
                        bounds = menuWindowToShow.getParentMenuWindow().getSubmenuBounds(submenuToShow.getBounds(), dim);
                        menuWindowToShow.show(bounds);
                    }
                    showingSubmenu = submenuToShow;
                }
            }
        } finally {
            XToolkit.awtUnlock();
        }
    }

    final void setItemsFont( Font font ) {
        XMenuItemPeer[] items = copyItems();
        int itemCnt = items.length;
        for (int i = 0; i < itemCnt; i++) {
            items[i].setFont(font);
        }
    }

    /************************************************
     *
     * Utility functions for manipulating mapped items
     *
     ************************************************/

    /**
     * Returns array of mapped items, null if error
     * This function has to be not synchronized
     * and we have to guarantee that we return
     * some MappingData to user. It's OK if
     * this.mappingData is replaced meanwhile
     */
    MappingData getMappingData() {
        MappingData mappingData = this.mappingData;
        if (mappingData == null) {
            mappingData = map();
            this.mappingData = mappingData;
        }
        return (MappingData)mappingData.clone();
    }

    /**
     * returns item thats mapped coordinates contain
     * specified point, null of none.
     * @param pt the point in this window's coordinate system
     */
    XMenuItemPeer getItemFromPoint(Point pt) {
        XMenuItemPeer[] items = getMappingData().getItems();
        int cnt = items.length;
        for (int i = 0; i < cnt; i++) {
            if (items[i].getBounds().contains(pt)) {
                return items[i];
            }
        }
        return null;
    }

    /**
     * Returns first item after currently selected
     * item that can be selected according to mapping array.
     * (no separators and no disabled items).
     * Currently selected item if it's only selectable,
     * null if no item can be selected
     */
    XMenuItemPeer getNextSelectableItem() {
        XMenuItemPeer[] mappedItems = getMappingData().getItems();
        XMenuItemPeer selectedItem = getSelectedItem();
        int cnt = mappedItems.length;
        //Find index of selected item
        int selIdx = -1;
        for (int i = 0; i < cnt; i++) {
            if (mappedItems[i] == selectedItem) {
                selIdx = i;
                break;
            }
        }
        int idx = (selIdx == cnt - 1) ? 0 : selIdx + 1;
        //cycle through mappedItems to find selectable item
        //beginning from the next item and moving to the
        //beginning of array when end is reached.
        //Cycle is finished on selected item itself
        for (int i = 0; i < cnt; i++) {
            XMenuItemPeer item = mappedItems[idx];
            if (!item.isSeparator() && item.isTargetItemEnabled()) {
                return item;
            }
            idx++;
            if (idx >= cnt) {
                idx = 0;
            }
        }
        //return null if no selectable item was found
        return null;
    }

    /**
     * Returns first item before currently selected
     * see getNextSelectableItem() for comments
     */
    XMenuItemPeer getPrevSelectableItem() {
        XMenuItemPeer[] mappedItems = getMappingData().getItems();
        XMenuItemPeer selectedItem = getSelectedItem();
        int cnt = mappedItems.length;
        //Find index of selected item
        int selIdx = -1;
        for (int i = 0; i < cnt; i++) {
            if (mappedItems[i] == selectedItem) {
                selIdx = i;
                break;
            }
        }
        int idx = (selIdx <= 0) ? cnt - 1 : selIdx - 1;
        //cycle through mappedItems to find selectable item
        for (int i = 0; i < cnt; i++) {
            XMenuItemPeer item = mappedItems[idx];
            if (!item.isSeparator() && item.isTargetItemEnabled()) {
                return item;
            }
            idx--;
            if (idx < 0) {
                idx = cnt - 1;
            }
        }
        //return null if no selectable item was found
        return null;
    }

    /**
     * Returns first selectable item
     * This function is intended for clearing selection
     */
    XMenuItemPeer getFirstSelectableItem() {
        XMenuItemPeer[] mappedItems = getMappingData().getItems();
        int cnt = mappedItems.length;
        for (int i = 0; i < cnt; i++) {
            XMenuItemPeer item = mappedItems[i];
            if (!item.isSeparator() && item.isTargetItemEnabled()) {
                return item;
            }
        }

        return null;
    }

    /************************************************
     *
     * Utility functions for manipulating
     * hierarchy of windows
     *
     ************************************************/

    /**
     * returns leaf menu window or
     * this if no children are showing
     */
    XBaseMenuWindow getShowingLeaf() {
        synchronized(getMenuTreeLock()) {
            XBaseMenuWindow leaf = this;
            XMenuPeer leafchild = leaf.getShowingSubmenu();
            while (leafchild != null) {
                leaf = leafchild.getMenuWindow();
                leafchild = leaf.getShowingSubmenu();
            }
            return leaf;
        }
    }

    /**
     * returns root menu window
     * or this if this window is topmost
     */
    XBaseMenuWindow getRootMenuWindow() {
        synchronized(getMenuTreeLock()) {
            XBaseMenuWindow t = this;
            XBaseMenuWindow tparent = t.getParentMenuWindow();
            while (tparent != null) {
                t = tparent;
                tparent = t.getParentMenuWindow();
            }
            return t;
        }
    }

    /**
     * Returns window that contains pt.
     * search is started from leaf window
     * to return first window in Z-order
     * @param pt point in global coordinates
     */
    XBaseMenuWindow getMenuWindowFromPoint(Point pt) {
        synchronized(getMenuTreeLock()) {
            XBaseMenuWindow t = getShowingLeaf();
            while (t != null) {
                Rectangle r = new Rectangle(t.toGlobal(new Point(0, 0)), t.getSize());
                if (r.contains(pt)) {
                    return t;
                }
                t = t.getParentMenuWindow();
            }
            return null;
        }
    }

    /************************************************
     *
     * Primitives for getSubmenuBounds
     *
     * These functions are invoked from getSubmenuBounds
     * implementations in different order. They check if window
     * of size windowSize fits to the specified edge of
     * rectangle itemBounds on the screen of screenSize.
     * Return rectangle that occupies the window if it fits or null.
     *
     ************************************************/

    /**
     * Checks if window fits below specified item
     * returns rectangle that the window fits to or null.
     * @param itemBounds rectangle of item in global coordinates
     * @param windowSize size of submenu window to fit
     * @param screenSize size of screen
     */
    Rectangle fitWindowBelow(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) {
        int width = windowSize.width;
        int height = windowSize.height;
        //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
        //near the periphery of the screen, XToolkit
        //Window should be moved if it's outside top-left screen bounds
        int x = (itemBounds.x > 0) ? itemBounds.x : 0;
        int y = (itemBounds.y + itemBounds.height > 0) ? itemBounds.y + itemBounds.height : 0;
        if (y + height <= screenSize.height) {
            //move it to the left if needed
            if (width > screenSize.width) {
                width = screenSize.width;
            }
            if (x + width > screenSize.width) {
                x = screenSize.width - width;
            }
            return new Rectangle(x, y, width, height);
        } else {
            return null;
        }
    }

    /**
     * Checks if window fits above specified item
     * returns rectangle that the window fits to or null.
     * @param itemBounds rectangle of item in global coordinates
     * @param windowSize size of submenu window to fit
     * @param screenSize size of screen
     */
    Rectangle fitWindowAbove(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) {
        int width = windowSize.width;
        int height = windowSize.height;
        //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
        //near the periphery of the screen, XToolkit
        //Window should be moved if it's outside bottom-left screen bounds
        int x = (itemBounds.x > 0) ? itemBounds.x : 0;
        int y = (itemBounds.y > screenSize.height) ? screenSize.height - height : itemBounds.y - height;
        if (y >= 0) {
            //move it to the left if needed
            if (width > screenSize.width) {
                width = screenSize.width;
            }
            if (x + width > screenSize.width) {
                x = screenSize.width - width;
            }
            return new Rectangle(x, y, width, height);
        } else {
            return null;
        }
    }

    /**
     * Checks if window fits to the right specified item
     * returns rectangle that the window fits to or null.
     * @param itemBounds rectangle of item in global coordinates
     * @param windowSize size of submenu window to fit
     * @param screenSize size of screen
     */
    Rectangle fitWindowRight(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) {
        int width = windowSize.width;
        int height = windowSize.height;
        //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
        //near the periphery of the screen, XToolkit
        //Window should be moved if it's outside top-left screen bounds
        int x = (itemBounds.x + itemBounds.width > 0) ? itemBounds.x + itemBounds.width : 0;
        int y = (itemBounds.y > 0) ? itemBounds.y : 0;
        if (x + width <= screenSize.width) {
            //move it to the top if needed
            if (height > screenSize.height) {
                height = screenSize.height;
            }
            if (y + height > screenSize.height) {
                y = screenSize.height - height;
            }
            return new Rectangle(x, y, width, height);
        } else {
            return null;
        }
    }

    /**
     * Checks if window fits to the left specified item
     * returns rectangle that the window fits to or null.
     * @param itemBounds rectangle of item in global coordinates
     * @param windowSize size of submenu window to fit
     * @param screenSize size of screen
     */
    Rectangle fitWindowLeft(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) {
        int width = windowSize.width;
        int height = windowSize.height;
        //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
        //near the periphery of the screen, XToolkit
        //Window should be moved if it's outside top-right screen bounds
        int x = (itemBounds.x < screenSize.width) ? itemBounds.x - width : screenSize.width - width;
        int y = (itemBounds.y > 0) ? itemBounds.y : 0;
        if (x >= 0) {
            //move it to the top if needed
            if (height > screenSize.height) {
                height = screenSize.height;
            }
            if (y + height > screenSize.height) {
                y = screenSize.height - height;
            }
            return new Rectangle(x, y, width, height);
        } else {
            return null;
        }
    }

    /**
     * The last thing we can do with the window
     * to fit it on screen - move it to the
     * top-left edge and cut by screen dimensions
     * @param windowSize size of submenu window to fit
     * @param screenSize size of screen
     */
    Rectangle fitWindowToScreen(Dimension windowSize, Dimension screenSize) {
        int width = (windowSize.width < screenSize.width) ? windowSize.width : screenSize.width;
        int height = (windowSize.height < screenSize.height) ? windowSize.height : screenSize.height;
        return new Rectangle(0, 0, width, height);
    }


    /************************************************
     *
     * Utility functions for manipulating colors
     *
     ************************************************/

    /**
     * This function is called before every painting.
     * TODO:It would be better to add PropertyChangeListener
     * to target component
     * TODO:It would be better to access background color
     * not invoking user-overridable function
     */
    void resetColors() {
        replaceColors((target == null) ? SystemColor.window : target.getBackground());
    }

    /**
     * Calculates colors of various elements given
     * background color. Uses MotifColorUtilities
     * @param backgroundColor the color of menu window's
     * background.
     */
    void replaceColors(Color backgroundColor) {
        if (backgroundColor != this.backgroundColor) {
            this.backgroundColor = backgroundColor;

            int red = backgroundColor.getRed();
            int green = backgroundColor.getGreen();
            int blue = backgroundColor.getBlue();

            foregroundColor = new Color(MotifColorUtilities.calculateForegroundFromBackground(red,green,blue));
            lightShadowColor = new Color(MotifColorUtilities.calculateTopShadowFromBackground(red,green,blue));
            darkShadowColor = new Color(MotifColorUtilities.calculateBottomShadowFromBackground(red,green,blue));
            selectedColor = new Color(MotifColorUtilities.calculateSelectFromBackground(red,green,blue));
            disabledColor = (backgroundColor.equals(Color.BLACK)) ? foregroundColor.darker() : backgroundColor.darker();
        }
    }

    Color getBackgroundColor() {
        return backgroundColor;
    }

    Color getForegroundColor() {
        return foregroundColor;
    }

    Color getLightShadowColor() {
        return lightShadowColor;
    }

    Color getDarkShadowColor() {
        return darkShadowColor;
    }

    Color getSelectedColor() {
        return selectedColor;
    }

    Color getDisabledColor() {
        return disabledColor;
    }

    /************************************************
     *
     * Painting utility functions
     *
     ************************************************/

    /**
     * Draws raised or sunken rectangle on specified graphics
     * @param g the graphics on which to draw
     * @param x the coordinate of left edge in coordinates of graphics
     * @param y the coordinate of top edge in coordinates of graphics
     * @param width the width of rectangle
     * @param height the height of rectangle
     * @param raised true to draw raised rectangle, false to draw sunken
     */
    void draw3DRect(Graphics g, int x, int y, int width, int height, boolean raised) {
        if ((width <= 0) || (height <= 0)) {
            return;
        }
        Color c = g.getColor();
        g.setColor(raised ? getLightShadowColor() : getDarkShadowColor());
        g.drawLine(x, y, x, y + height - 1);
        g.drawLine(x + 1, y, x + width - 1, y);
        g.setColor(raised ? getDarkShadowColor() : getLightShadowColor());
        g.drawLine(x + 1, y + height - 1, x + width - 1, y + height - 1);
        g.drawLine(x + width - 1, y + 1, x + width - 1, y + height - 1);
        g.setColor(c);
    }

    /************************************************
     *
     * Overriden utility functions of XWindow
     *
     ************************************************/

    /**
     * Filters X events
     */
     protected boolean isEventDisabled(XEvent e) {
        switch (e.get_type()) {
          case XConstants.Expose :
          case XConstants.GraphicsExpose :
          case XConstants.ButtonPress:
          case XConstants.ButtonRelease:
          case XConstants.MotionNotify:
          case XConstants.KeyPress:
          case XConstants.KeyRelease:
          case XConstants.DestroyNotify:
              return super.isEventDisabled(e);
          default:
              return true;
        }
    }

    /**
     * Invokes disposal procedure on eventHandlerThread
     */
    public void dispose() {
        setDisposed(true);
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                doDispose();
            }
        });
    }

    /**
     * Performs disposal of menu window.
     * Should be called only on eventHandlerThread
     */
    protected void doDispose() {
        xSetVisible(false);
        SurfaceData oldData = surfaceData;
        surfaceData = null;
        if (oldData != null) {
            oldData.invalidate();
        }
        XToolkit.targetDisposedPeer(target, this);
        destroy();
    }

    /**
     * Invokes event processing on eventHandlerThread
     * This function needs to be overriden since
     * XBaseMenuWindow has no corresponding component
     * so events can not be processed using standart means
     */
    void postEvent(final AWTEvent event) {
        EventQueue.invokeLater(new Runnable() {
                public void run() {
                    handleEvent(event);
                }
            });
    }

    /**
     * The implementation of base window performs processing
     * of paint events only. This behaviour is changed in
     * descendants.
     */
    protected void handleEvent(AWTEvent event) {
        switch(event.getID()) {
        case PaintEvent.PAINT:
            doHandleJavaPaintEvent((PaintEvent)event);
            break;
        }
    }

    /**
     * Save location of pointer for further use
     * then invoke superclass
     */
    public boolean grabInput() {
        int rootX;
        int rootY;
        boolean res;
        XToolkit.awtLock();
        try {
            long root = XlibWrapper.RootWindow(XToolkit.getDisplay(),
                    getScreenNumber());
            res = XlibWrapper.XQueryPointer(XToolkit.getDisplay(), root,
                                            XlibWrapper.larg1, //root
                                            XlibWrapper.larg2, //child
                                            XlibWrapper.larg3, //root_x
                                            XlibWrapper.larg4, //root_y
                                            XlibWrapper.larg5, //child_x
                                            XlibWrapper.larg6, //child_y
                                            XlibWrapper.larg7);//mask
            rootX = Native.getInt(XlibWrapper.larg3);
            rootY = Native.getInt(XlibWrapper.larg4);
            res &= super.grabInput();
        } finally {
            XToolkit.awtUnlock();
        }
        if (res) {
            //Mouse pointer is on the same display
            this.grabInputPoint = new Point(rootX, rootY);
            this.hasPointerMoved = false;
        } else {
            this.grabInputPoint = null;
            this.hasPointerMoved = true;
        }
        return res;
    }
    /************************************************
     *
     * Overridable event processing functions
     *
     ************************************************/

    /**
     * Performs repainting
     */
    void doHandleJavaPaintEvent(PaintEvent event) {
        Rectangle rect = event.getUpdateRect();
        repaint(rect.x, rect.y, rect.width, rect.height);
    }

    /************************************************
     *
     * User input handling utility functions
     *
     ************************************************/

    /**
     * Performs handling of java mouse event
     * Note that this function should be invoked
     * only from root of menu window's hierarchy
     * that grabs input focus
     */
    void doHandleJavaMouseEvent( MouseEvent mouseEvent ) {
        if (!XToolkit.isLeftMouseButton(mouseEvent) && !XToolkit.isRightMouseButton(mouseEvent)) {
            return;
        }
        //Window that owns input
        XBaseWindow grabWindow = XAwtState.getGrabWindow();
        //Point of mouse event in global coordinates
        Point ptGlobal = mouseEvent.getLocationOnScreen();
        if (!hasPointerMoved) {
            //Fix for 6301307: NullPointerException while dispatching mouse events, XToolkit
            if (grabInputPoint == null ||
                (Math.abs(ptGlobal.x - grabInputPoint.x) > getMouseMovementSmudge()) ||
                (Math.abs(ptGlobal.y - grabInputPoint.y) > getMouseMovementSmudge())) {
                hasPointerMoved = true;
            }
        }
        //Z-order first descendant of current menu window
        //hierarchy that contain mouse point
        XBaseMenuWindow wnd = getMenuWindowFromPoint(ptGlobal);
        //Item in wnd that contains mouse point, if any
        XMenuItemPeer item = (wnd != null) ? wnd.getItemFromPoint(wnd.toLocal(ptGlobal)) : null;
        //Currently showing leaf window
        XBaseMenuWindow cwnd = getShowingLeaf();
        switch (mouseEvent.getID()) {
          case MouseEvent.MOUSE_PRESSED:
              //This line is to get rid of possible problems
              //That may occur if mouse events are lost
              showingMousePressedSubmenu = null;
              if ((grabWindow == this) && (wnd == null)) {
                  //Menus grab input and the user
                  //presses mouse button outside
                  ungrabInput();
              } else {
                  //Menus grab input OR mouse is pressed on menu window
                  grabInput();
                  if (item != null && !item.isSeparator() && item.isTargetItemEnabled()) {
                      //Button is pressed on enabled item
                      if (wnd.getShowingSubmenu() == item) {
                          //Button is pressed on item that shows
                          //submenu. We have to hide its submenu
                          //if user clicks on it
                          showingMousePressedSubmenu = (XMenuPeer)item;
                      }
                      wnd.selectItem(item, true);
                  } else {
                      //Button is pressed on disabled item or empty space
                      if (wnd != null) {
                          wnd.selectItem(null, false);
                      }
                  }
              }
              break;
          case MouseEvent.MOUSE_RELEASED:
              //Note that if item is not null, wnd has to be not null
              if (item != null && !item.isSeparator() && item.isTargetItemEnabled()) {
                  if  (item instanceof XMenuPeer) {
                      if (showingMousePressedSubmenu == item) {
                          //User clicks on item that shows submenu.
                          //Hide the submenu
                          if (wnd instanceof XMenuBarPeer) {
                              ungrabInput();
                          } else {
                              wnd.selectItem(item, false);
                          }
                      }
                  } else {
                      //Invoke action event
                      item.action(mouseEvent.getWhen());
                      ungrabInput();
                  }
              } else {
                  //Mouse is released outside menu items
                  if (hasPointerMoved || (wnd instanceof XMenuBarPeer)) {
                      ungrabInput();
                  }
              }
              showingMousePressedSubmenu = null;
              break;
          case MouseEvent.MOUSE_DRAGGED:
              if (wnd != null) {
                  //Mouse is dragged over menu window
                  //Move selection to item under cursor
                  if (item != null && !item.isSeparator() && item.isTargetItemEnabled()) {
                      if (grabWindow == this){
                          wnd.selectItem(item, true);
                      }
                  } else {
                      wnd.selectItem(null, false);
                  }
              } else {
                  //Mouse is dragged outside menu windows
                  //clear selection in leaf to reflect it
                  if (cwnd != null) {
                      cwnd.selectItem(null, false);
                  }
              }
              break;
        }
    }

    /**
     * Performs handling of java keyboard event
     * Note that this function should be invoked
     * only from root of menu window's hierarchy
     * that grabs input focus
     */
    void doHandleJavaKeyEvent(KeyEvent event) {
        if (log.isLoggable(Level.FINER)) log.finer(event.toString());
        if (event.getID() != KeyEvent.KEY_PRESSED) {
            return;
        }
        final int keyCode = event.getKeyCode();
        XBaseMenuWindow cwnd = getShowingLeaf();
        XMenuItemPeer citem = cwnd.getSelectedItem();
        switch(keyCode) {
          case KeyEvent.VK_UP:
          case KeyEvent.VK_KP_UP:
              if (!(cwnd instanceof XMenuBarPeer)) {
                  //If active window is not menu bar,
                  //move selection up
                  cwnd.selectItem(cwnd.getPrevSelectableItem(), false);
              }
              break;
          case KeyEvent.VK_DOWN:
          case KeyEvent.VK_KP_DOWN:
              if (cwnd instanceof XMenuBarPeer) {
                  //If active window is menu bar show current submenu
                  selectItem(getSelectedItem(), true);
              } else {
                  //move selection down
                  cwnd.selectItem(cwnd.getNextSelectableItem(), false);
              }
              break;
          case KeyEvent.VK_LEFT:
          case KeyEvent.VK_KP_LEFT:
              if (cwnd instanceof XMenuBarPeer) {
                  //leaf window is menu bar
                  //select previous item
                  selectItem(getPrevSelectableItem(), false);
              } else if (cwnd.getParentMenuWindow() instanceof XMenuBarPeer) {
                  //leaf window is direct child of menu bar
                  //select previous item of menu bar
                  //and show its submenu
                  selectItem(getPrevSelectableItem(), true);
              } else {
                  //hide leaf moving focus to its parent
                  //(equvivalent of pressing ESC)
                  XBaseMenuWindow pwnd = cwnd.getParentMenuWindow();
                  //Fix for 6272952: PIT: Pressing LEFT ARROW on a popup menu throws NullPointerException, XToolkit
                  if (pwnd != null) {
                      pwnd.selectItem(pwnd.getSelectedItem(), false);
                  }
              }
              break;
          case KeyEvent.VK_RIGHT:
          case KeyEvent.VK_KP_RIGHT:
              if (cwnd instanceof XMenuBarPeer) {
                  //leaf window is menu bar
                  //select next item
                  selectItem(getNextSelectableItem(), false);
              } else if (citem instanceof XMenuPeer) {
                  //current item is menu, show its window
                  //(equivalent of ENTER)
                  cwnd.selectItem(citem, true);
              } else if (this instanceof XMenuBarPeer) {
                  //if this is menu bar (not popup menu)
                  //and the user presses RIGHT on item (not submenu)
                  //select next top-level menu
                  selectItem(getNextSelectableItem(), true);
              }
              break;
          case KeyEvent.VK_SPACE:
          case KeyEvent.VK_ENTER:
              //If the current item has submenu show it
              //Perform action otherwise
              if (citem instanceof XMenuPeer) {
                  cwnd.selectItem(citem, true);
              } else if (citem != null) {
                  citem.action(event.getWhen());
                  ungrabInput();
              }
              break;
          case KeyEvent.VK_ESCAPE:
              //If current window is menu bar or its child - close it
              //If current window is popup menu - close it
              //go one level up otherwise

              //Fixed 6266513: Incorrect key handling in XAWT popup menu
              //Popup menu should be closed on 'ESC'
              if ((cwnd instanceof XMenuBarPeer) || (cwnd.getParentMenuWindow() instanceof XMenuBarPeer)) {
                  ungrabInput();
              } else if (cwnd instanceof XPopupMenuPeer) {
                  ungrabInput();
              } else {
                  XBaseMenuWindow pwnd = cwnd.getParentMenuWindow();
                  pwnd.selectItem(pwnd.getSelectedItem(), false);
              }
              break;
          case KeyEvent.VK_F10:
              //Fixed 6266513: Incorrect key handling in XAWT popup menu
              //All menus should be closed on 'F10'
              ungrabInput();
              break;
          default:
              break;
        }
    }

} //class XBaseMenuWindow