| /* | |
| * Copyright (c) 2002, 2023, 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.Dimension; | |
| import java.awt.Graphics; | |
| import java.awt.MenuItem; | |
| import java.awt.Point; | |
| import java.awt.Rectangle; | |
| import java.awt.Toolkit; | |
| import java.awt.Window; | |
| import java.util.Vector; | |
| import sun.awt.SunToolkit; | |
| import sun.util.logging.PlatformLogger; | |
| import javax.swing.SwingUtilities; | |
| public class XMenuWindow extends XBaseMenuWindow { | |
| /************************************************ | |
| * | |
| * Data members | |
| * | |
| ************************************************/ | |
| private static PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XMenuWindow"); | |
| /* | |
| * Primary members | |
| */ | |
| private XMenuPeer menuPeer; | |
| /* | |
| * dimension constants | |
| */ | |
| private static final int WINDOW_SPACING_LEFT = 2; | |
| private static final int WINDOW_SPACING_RIGHT = 2; | |
| private static final int WINDOW_SPACING_TOP = 2; | |
| private static final int WINDOW_SPACING_BOTTOM = 2; | |
| private static final int WINDOW_ITEM_INDENT = 15; | |
| private static final int WINDOW_ITEM_MARGIN_LEFT = 2; | |
| private static final int WINDOW_ITEM_MARGIN_RIGHT = 2; | |
| private static final int WINDOW_ITEM_MARGIN_TOP = 2; | |
| private static final int WINDOW_ITEM_MARGIN_BOTTOM = 2; | |
| private static final int WINDOW_SHORTCUT_SPACING = 10; | |
| /* | |
| * Checkmark | |
| */ | |
| private static final int CHECKMARK_SIZE = 128; | |
| private static final int[] CHECKMARK_X = new int[] {1, 25,56,124,124,85, 64}; // X-coords | |
| private static final int[] CHECKMARK_Y = new int[] {59,35,67, 0, 12,66,123}; // Y-coords | |
| /************************************************ | |
| * | |
| * Mapping data | |
| * | |
| ************************************************/ | |
| static final class MappingData extends XBaseMenuWindow.MappingData { | |
| /** | |
| * Rectangle for the caption | |
| * Necessary to fix 6267144: PIT: Popup menu label is not shown, XToolkit | |
| */ | |
| private Rectangle captionRect; | |
| /** | |
| * Desired size of menu window | |
| */ | |
| private Dimension desiredSize; | |
| /** | |
| * Width of largest checkmark | |
| * At the same time the left origin | |
| * of all item's text | |
| */ | |
| private int leftMarkWidth; | |
| /** | |
| * Left origin of all shortcut labels | |
| */ | |
| private int shortcutOrigin; | |
| /** | |
| * The origin of right mark | |
| * (submenu's arrow) | |
| */ | |
| private int rightMarkOrigin; | |
| MappingData(XMenuItemPeer[] items, Rectangle captionRect, Dimension desiredSize, int leftMarkWidth, int shortcutOrigin, int rightMarkOrigin) { | |
| super(items); | |
| this.captionRect = captionRect; | |
| this.desiredSize = desiredSize; | |
| this.leftMarkWidth = leftMarkWidth; | |
| this.shortcutOrigin = shortcutOrigin; | |
| this.rightMarkOrigin = rightMarkOrigin; | |
| } | |
| /** | |
| * Constructs MappingData without items | |
| * This constructor should be used in case of errors | |
| */ | |
| MappingData() { | |
| this.desiredSize = new Dimension(0, 0); | |
| this.leftMarkWidth = 0; | |
| this.shortcutOrigin = 0; | |
| this.rightMarkOrigin = 0; | |
| } | |
| public Rectangle getCaptionRect() { | |
| return this.captionRect; | |
| } | |
| public Dimension getDesiredSize() { | |
| return this.desiredSize; | |
| } | |
| public int getShortcutOrigin() { | |
| return this.shortcutOrigin; | |
| } | |
| public int getLeftMarkWidth() { | |
| return this.leftMarkWidth; | |
| } | |
| public int getRightMarkOrigin() { | |
| return this.rightMarkOrigin; | |
| } | |
| } | |
| /************************************************ | |
| * | |
| * Construction | |
| * | |
| ************************************************/ | |
| /** | |
| * Constructs XMenuWindow for specified XMenuPeer | |
| * null for XPopupMenuWindow | |
| */ | |
| XMenuWindow(XMenuPeer menuPeer) { | |
| if (menuPeer != null) { | |
| this.menuPeer = menuPeer; | |
| this.target = menuPeer.getContainer().target; | |
| // Get menus from the target. | |
| Vector<MenuItem> targetItemVector = null; | |
| targetItemVector = getMenuTargetItems(); | |
| reloadItems(targetItemVector); | |
| } | |
| } | |
| /************************************************ | |
| * | |
| * Initialization | |
| * | |
| ************************************************/ | |
| /* | |
| * Overridden initialization | |
| */ | |
| @Override | |
| void postInit(XCreateWindowParams params) { | |
| super.postInit(params); | |
| //Fixed 6267182: PIT: Menu is not visible after | |
| //showing and disposing a file dialog, XToolkit | |
| //toFront() is called on every show | |
| } | |
| /************************************************ | |
| * | |
| * Implementation of abstract methods | |
| * | |
| ************************************************/ | |
| /** | |
| * @see XBaseMenuWindow#getParentMenuWindow() | |
| */ | |
| @Override | |
| protected XBaseMenuWindow getParentMenuWindow() { | |
| return (menuPeer != null) ? menuPeer.getContainer() : null; | |
| } | |
| /** | |
| * @see XBaseMenuWindow#map() | |
| */ | |
| @Override | |
| protected MappingData map() { | |
| //TODO:Implement popup-menu caption mapping and painting and tear-off | |
| int itemCnt; | |
| if (!isCreated()) { | |
| MappingData mappingData = new MappingData(new XMenuItemPeer[0], new Rectangle(0, 0, 0, 0), new Dimension(0, 0), 0, 0, 0); | |
| return mappingData; | |
| } | |
| XMenuItemPeer[] itemVector = copyItems(); | |
| itemCnt = itemVector.length; | |
| //We need maximum width of components before calculating item's bounds | |
| Dimension captionSize = getCaptionSize(); | |
| int maxWidth = (captionSize != null) ? captionSize.width : 0; | |
| int maxLeftIndent = 0; | |
| int maxRightIndent = 0; | |
| int maxShortcutWidth = 0; | |
| XMenuItemPeer.TextMetrics[] itemMetrics = new XMenuItemPeer.TextMetrics[itemCnt]; | |
| for (int i = 0; i < itemCnt; i++) { | |
| XMenuItemPeer item = itemVector[i]; | |
| itemMetrics[i] = itemVector[i].getTextMetrics(); | |
| Dimension dim = itemMetrics[i].getTextDimension(); | |
| if (dim != null) { | |
| if (itemVector[i] instanceof XCheckboxMenuItemPeer) { | |
| maxLeftIndent = Math.max(maxLeftIndent, dim.height); | |
| } else if (itemVector[i] instanceof XMenuPeer) { | |
| maxRightIndent = Math.max(maxRightIndent, dim.height); | |
| } | |
| maxWidth = Math.max(maxWidth, dim.width); | |
| maxShortcutWidth = Math.max(maxShortcutWidth, itemMetrics[i].getShortcutWidth()); | |
| } | |
| } | |
| //Calculate bounds | |
| int nextOffset = WINDOW_SPACING_TOP; | |
| int shortcutOrigin = WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent + maxWidth; | |
| if (maxShortcutWidth > 0) { | |
| shortcutOrigin = shortcutOrigin + WINDOW_SHORTCUT_SPACING; | |
| } | |
| int rightMarkOrigin = shortcutOrigin + maxShortcutWidth; | |
| int itemWidth = rightMarkOrigin + maxRightIndent + WINDOW_ITEM_MARGIN_RIGHT; | |
| int width = WINDOW_SPACING_LEFT + itemWidth + WINDOW_SPACING_RIGHT; | |
| //Caption rectangle | |
| Rectangle captionRect = null; | |
| if (captionSize != null) { | |
| captionRect = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, itemWidth, captionSize.height); | |
| nextOffset += captionSize.height; | |
| } else { | |
| captionRect = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, maxWidth, 0); | |
| } | |
| //Item rectangles | |
| for (int i = 0; i < itemCnt; i++) { | |
| XMenuItemPeer item = itemVector[i]; | |
| XMenuItemPeer.TextMetrics metrics = itemMetrics[i]; | |
| Dimension dim = metrics.getTextDimension(); | |
| if (dim != null) { | |
| int itemHeight = WINDOW_ITEM_MARGIN_TOP + dim.height + WINDOW_ITEM_MARGIN_BOTTOM; | |
| Rectangle bounds = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, itemWidth, itemHeight); | |
| int y = (itemHeight + dim.height) / 2 - metrics.getTextBaseline(); | |
| Point textOrigin = new Point(WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent, nextOffset + y); | |
| nextOffset += itemHeight; | |
| item.map(bounds, textOrigin); | |
| } else { | |
| //Text metrics could not be determined because of errors | |
| //Map item with empty rectangle | |
| Rectangle bounds = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, 0, 0); | |
| Point textOrigin = new Point(WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent, nextOffset); | |
| item.map(bounds, textOrigin); | |
| } | |
| } | |
| int height = nextOffset + WINDOW_SPACING_BOTTOM; | |
| MappingData mappingData = new MappingData(itemVector, captionRect, new Dimension(width, height), maxLeftIndent, shortcutOrigin, rightMarkOrigin); | |
| return mappingData; | |
| } | |
| /** | |
| * @see XBaseMenuWindow#getSubmenuBounds | |
| */ | |
| @Override | |
| protected Rectangle getSubmenuBounds(Rectangle itemBounds, Dimension windowSize) { | |
| Rectangle globalBounds = toGlobal(itemBounds); | |
| Rectangle screenBounds = getCurrentGraphicsConfiguration().getBounds(); | |
| Rectangle res; | |
| res = fitWindowRight(globalBounds, windowSize, screenBounds); | |
| if (res != null) { | |
| return res; | |
| } | |
| res = fitWindowBelow(globalBounds, windowSize, screenBounds); | |
| if (res != null) { | |
| return res; | |
| } | |
| res = fitWindowAbove(globalBounds, windowSize, screenBounds); | |
| if (res != null) { | |
| return res; | |
| } | |
| res = fitWindowLeft(globalBounds, windowSize, screenBounds); | |
| if (res != null) { | |
| return res; | |
| } | |
| return fitWindowToScreen(windowSize, screenBounds); | |
| } | |
| /** | |
| * It's likely that size of items was changed | |
| * invoke resizing of window on eventHandlerThread | |
| */ | |
| @Override | |
| protected void updateSize() { | |
| resetMapping(); | |
| if (isShowing()) { | |
| XToolkit.executeOnEventHandlerThread(target, new Runnable() { | |
| public void run() { | |
| Dimension dim = getDesiredSize(); | |
| reshape(x, y, dim.width, dim.height); | |
| } | |
| }); | |
| } | |
| } | |
| /************************************************ | |
| * | |
| * Overridable caption-painting functions | |
| * Necessary to fix 6267144: PIT: Popup menu label is not shown, XToolkit | |
| * | |
| ************************************************/ | |
| /** | |
| * Returns size of menu window's caption or null | |
| * if window has no caption. | |
| * Can be overridden for popup menus and tear-off menus | |
| */ | |
| protected Dimension getCaptionSize() { | |
| return null; | |
| } | |
| /** | |
| * Paints menu window's caption. | |
| * Can be overridden for popup menus and tear-off menus. | |
| * Default implementation does nothing | |
| */ | |
| protected void paintCaption(Graphics g, Rectangle rect) { | |
| } | |
| /************************************************ | |
| * | |
| * General-purpose utility functions | |
| * | |
| ************************************************/ | |
| /** | |
| * Returns corresponding menu peer | |
| */ | |
| XMenuPeer getMenuPeer() { | |
| return menuPeer; | |
| } | |
| /** | |
| * Reads vector of items from target | |
| * This function is overridden in XPopupMenuPeer | |
| */ | |
| Vector<MenuItem> getMenuTargetItems() { | |
| return menuPeer.getTargetItems(); | |
| } | |
| /** | |
| * Returns desired size calculated while mapping | |
| */ | |
| Dimension getDesiredSize() { | |
| MappingData mappingData = (MappingData)getMappingData(); | |
| return mappingData.getDesiredSize(); | |
| } | |
| /** | |
| * Checks if menu window is created | |
| */ | |
| boolean isCreated() { | |
| return getWindow() != 0; | |
| } | |
| /** | |
| * Performs delayed creation of menu window if necessary | |
| */ | |
| boolean ensureCreated() { | |
| if (!isCreated()) { | |
| XCreateWindowParams params = getDelayedParams(); | |
| params.remove(DELAYED); | |
| params.add(OVERRIDE_REDIRECT, Boolean.TRUE); | |
| params.add(XWindow.TARGET, target); | |
| init(params); | |
| } | |
| return true; | |
| } | |
| protected Window getMenuTarget() { | |
| if (target instanceof Window targetWindow) { | |
| return targetWindow; | |
| } else { | |
| return target == null | |
| ? null | |
| : SwingUtilities.getWindowAncestor(target); | |
| } | |
| } | |
| /** | |
| * Init window if it's not inited yet | |
| * and show it at specified coordinates | |
| * @param bounds bounding rectangle of window | |
| * in global coordinates | |
| */ | |
| void show(Rectangle bounds) { | |
| if (!isCreated()) { | |
| return; | |
| } | |
| if (log.isLoggable(PlatformLogger.Level.FINER)) { | |
| log.finer("showing menu window + " + getWindow() + " at " + bounds); | |
| } | |
| XToolkit.awtLock(); | |
| try { | |
| reshape(bounds.x, bounds.y, bounds.width, bounds.height); | |
| if (Toolkit.getDefaultToolkit() instanceof SunToolkit sunToolkit) { | |
| sunToolkit.dismissPopupOnFocusLostIfNeeded(getMenuTarget()); | |
| } | |
| xSetVisible(true); | |
| //Fixed 6267182: PIT: Menu is not visible after | |
| //showing and disposing a file dialog, XToolkit | |
| toFront(); | |
| selectItem(getFirstSelectableItem(), false); | |
| } finally { | |
| XToolkit.awtUnlock(); | |
| } | |
| } | |
| /** | |
| * Hides menu window | |
| */ | |
| void hide() { | |
| selectItem(null, false); | |
| xSetVisible(false); | |
| if (Toolkit.getDefaultToolkit() instanceof SunToolkit sunToolkit) { | |
| sunToolkit.dismissPopupOnFocusLostIfNeededCleanUp(getMenuTarget()); | |
| } | |
| } | |
| /************************************************ | |
| * | |
| * Painting | |
| * | |
| ************************************************/ | |
| /** | |
| * Paints menu window | |
| */ | |
| @Override | |
| public void paintPeer(Graphics g) { | |
| resetColors(); | |
| int width = getWidth(); | |
| int height = getHeight(); | |
| flush(); | |
| //Fill background of rectangle | |
| g.setColor(getBackgroundColor()); | |
| g.fillRect(1, 1, width - 2, height - 2); | |
| draw3DRect(g, 0, 0, width, height, true); | |
| //Mapping data | |
| MappingData mappingData = (MappingData)getMappingData(); | |
| //Paint caption | |
| paintCaption(g, mappingData.getCaptionRect()); | |
| //Paint menus | |
| XMenuItemPeer[] itemVector = mappingData.getItems(); | |
| Dimension windowSize = mappingData.getDesiredSize(); | |
| XMenuItemPeer selectedItem = getSelectedItem(); | |
| for (int i = 0; i < itemVector.length; i++) { | |
| XMenuItemPeer item = itemVector[i]; | |
| XMenuItemPeer.TextMetrics metrics = item.getTextMetrics(); | |
| Rectangle bounds = item.getBounds(); | |
| if (item.isSeparator()) { | |
| draw3DRect(g, bounds.x, bounds.y + bounds.height / 2, bounds.width, 2, false); | |
| } else { | |
| //paint item | |
| g.setFont(item.getTargetFont()); | |
| Point textOrigin = item.getTextOrigin(); | |
| Dimension textDim = metrics.getTextDimension(); | |
| if (item == selectedItem) { | |
| g.setColor(getSelectedColor()); | |
| g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); | |
| draw3DRect(g, bounds.x, bounds.y, bounds.width, bounds.height, false); | |
| } | |
| g.setColor(item.isTargetItemEnabled() ? getForegroundColor() : getDisabledColor()); | |
| g.drawString(item.getTargetLabel(), textOrigin.x, textOrigin.y); | |
| String shortcutText = item.getShortcutText(); | |
| if (shortcutText != null) { | |
| g.drawString(shortcutText, mappingData.getShortcutOrigin(), textOrigin.y); | |
| } | |
| if (item instanceof XMenuPeer) { | |
| //calculate arrow coordinates | |
| int markWidth = textDim.height * 4 / 5; | |
| int markHeight = textDim.height * 4 / 5; | |
| int markX = bounds.x + bounds.width - markWidth - WINDOW_SPACING_RIGHT - WINDOW_ITEM_MARGIN_RIGHT; | |
| int markY = bounds.y + (bounds.height - markHeight) / 2; | |
| //draw arrow | |
| g.setColor(item.isTargetItemEnabled() ? getDarkShadowColor() : getDisabledColor()); | |
| g.drawLine(markX, markY + markHeight, markX + markWidth, markY + markHeight / 2); | |
| g.setColor(item.isTargetItemEnabled() ? getLightShadowColor() : getDisabledColor()); | |
| g.drawLine(markX, markY, markX + markWidth, markY + markHeight / 2); | |
| g.drawLine(markX, markY, markX, markY + markHeight); | |
| } else if (item instanceof XCheckboxMenuItemPeer) { | |
| //calculate checkmark coordinates | |
| int markWidth = textDim.height * 4 / 5; | |
| int markHeight = textDim.height * 4 / 5; | |
| int markX = WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT; | |
| int markY = bounds.y + (bounds.height - markHeight) / 2; | |
| boolean checkState = ((XCheckboxMenuItemPeer)item).getTargetState(); | |
| //draw checkmark | |
| if (checkState) { | |
| g.setColor(getSelectedColor()); | |
| g.fillRect(markX, markY, markWidth, markHeight); | |
| draw3DRect(g, markX, markY, markWidth, markHeight, false); | |
| int[] px = new int[CHECKMARK_X.length]; | |
| int[] py = new int[CHECKMARK_X.length]; | |
| for (int j = 0; j < CHECKMARK_X.length; j++) { | |
| px[j] = markX + CHECKMARK_X[j] * markWidth / CHECKMARK_SIZE; | |
| py[j] = markY + CHECKMARK_Y[j] * markHeight / CHECKMARK_SIZE; | |
| } | |
| g.setColor(item.isTargetItemEnabled() ? getForegroundColor() : getDisabledColor()); | |
| g.fillPolygon(px, py, CHECKMARK_X.length); | |
| } else { | |
| g.setColor(getBackgroundColor()); | |
| g.fillRect(markX, markY, markWidth, markHeight); | |
| draw3DRect(g, markX, markY, markWidth, markHeight, true); | |
| } | |
| } | |
| } | |
| } | |
| flush(); | |
| } | |
| } |