diff -r fd16c54261b3 -r 90ce3da70b43 jdk/src/solaris/classes/sun/awt/X11/XBaseMenuWindow.java --- /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 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