/*
* Copyright (c) 2002, 2017, 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.BufferedImage;
import java.awt.geom.Point2D;
import java.util.Vector;
import sun.util.logging.PlatformLogger;
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 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
*
************************************************/
/*
* Overriden initialization
*/
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()
*/
protected XBaseMenuWindow getParentMenuWindow() {
return (menuPeer != null) ? menuPeer.getContainer() : null;
}
/**
* @see XBaseMenuWindow#map()
*/
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
*/
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
*/
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 overriden for popup menus and tear-off menus
*/
protected Dimension getCaptionSize() {
return null;
}
/**
* Paints menu window's caption.
* Can be overriden 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 overriden 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;
}
/**
* 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);
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);
}
/************************************************
*
* 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();
}
}