jdk/src/solaris/classes/sun/awt/X11/XBaseMenuWindow.java
changeset 2 90ce3da70b43
child 439 3488710b02f8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/solaris/classes/sun/awt/X11/XBaseMenuWindow.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1226 @@
+/*
+ * Copyright 2005-2007 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 XlibWrapper.Expose :
+          case XlibWrapper.GraphicsExpose :
+          case XlibWrapper.ButtonPress:
+          case XlibWrapper.ButtonRelease:
+          case XlibWrapper.MotionNotify:
+          case XlibWrapper.KeyPress:
+          case XlibWrapper.KeyRelease:
+          case XlibWrapper.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