| /* |
| * Copyright (c) 2005, 2015, Oracle and/or its affiliates. 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.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 sun.util.logging.PlatformLogger; |
| import sun.java2d.SurfaceData; |
| import sun.java2d.SunGraphics2D; |
| |
| /** |
| * The abstract class XBaseMenuWindow is the superclass |
| * of all menu windows. |
| */ |
| public abstract class XBaseMenuWindow extends XWindow { |
| |
| /************************************************ |
| * |
| * Data members |
| * |
| ************************************************/ |
| |
| private static PlatformLogger log = PlatformLogger.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. |
| */ |
| private static 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; |
| |
| private AppContext disposeAppContext; |
| |
| /************************************************ |
| * |
| * 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(ex); |
| } |
| } |
| |
| public XMenuItemPeer[] getItems() { |
| return this.items; |
| } |
| } |
| |
| /************************************************ |
| * |
| * Construction |
| * |
| ************************************************/ |
| XBaseMenuWindow() { |
| super(new XCreateWindowParams(new Object[] { |
| DELAYED, Boolean.TRUE})); |
| |
| disposeAppContext = AppContext.getAppContext(); |
| } |
| |
| /************************************************ |
| * |
| * Abstract methods |
| * |
| ************************************************/ |
| |
| /** |
| * Returns parent menu window (not the X-hierarchy 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 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 = AWTAccessor.getMenuComponentAccessor().getPeer(item); |
| if (mp != null) { |
| mp.setContainer(this); |
| synchronized(getMenuTreeLock()) { |
| items.add(mp); |
| } |
| } else { |
| if (log.isLoggable(PlatformLogger.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(PlatformLogger.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<? extends MenuItem> items) { |
| synchronized(getMenuTreeLock()) { |
| this.items.clear(); |
| MenuItem[] itemArray = 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(PlatformLogger.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(PlatformLogger.Level.FINEST)) { |
| 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); |
| |
| SunToolkit.invokeLaterOnAppContext(disposeAppContext, 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(); |
| } |
| 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) { |
| InvocationEvent ev = new InvocationEvent(event.getSource(), new Runnable() { |
| public void run() { |
| handleEvent(event); |
| } |
| }); |
| super.postEvent(ev); |
| } |
| |
| /** |
| * 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(), mouseEvent.getModifiers()); |
| 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(PlatformLogger.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(), event.getModifiers()); |
| 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 |