Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2011, 2024, 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 com.sun.javafx.scene.control;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javafx.animation.Animation.Status;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.Styleable;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.HPos;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Orientation;
import javafx.geometry.Side;
import javafx.geometry.VPos;
import javafx.scene.AccessibleAction;
import javafx.scene.AccessibleAttribute;
import javafx.scene.AccessibleRole;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.Skin;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.stage.Window;
import javafx.util.Duration;
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.scene.control.behavior.TwoLevelFocusPopupBehavior;
import com.sun.javafx.scene.control.skin.Utils;
import com.sun.javafx.scene.traversal.Direction;
/**
* This is a the SkinBase for ContextMenu based controls so that the CSS parts
* work right, because otherwise we would have to copy the Keys from there to here.
*/
public class ContextMenuContent extends Region {
private static final String ITEM_STYLE_CLASS_LISTENER = "itemStyleClassListener";
private ContextMenu contextMenu;
/***************************************************************************
* UI subcomponents
**************************************************************************/
private double maxGraphicWidth = 0; // we keep this margin to left for graphic
private double maxRightWidth = 0;
private double maxLabelWidth = 0;
private double maxRowHeight = 0;
private double maxLeftWidth = 0;
private double oldWidth = 0;
private Rectangle clipRect;
MenuBox itemsContainer;
private ArrowMenuItem upArrow;
private ArrowMenuItem downArrow;
/*
* We maintain a current focused index which is used
* in keyboard navigation of menu items.
*/
private int currentFocusedIndex = -1;
private boolean itemsDirty = true;
private InvalidationListener popupShowingListener = arg0 -> {
updateItems();
};
private WeakInvalidationListener weakPopupShowingListener =
new WeakInvalidationListener(popupShowingListener);
/***************************************************************************
* Constructors
**************************************************************************/
public ContextMenuContent(final ContextMenu popupMenu) {
this.contextMenu = popupMenu;
clipRect = new Rectangle();
clipRect.setSmooth(false);
itemsContainer = new MenuBox();
// itemsContainer = new VBox();
itemsContainer.setClip(clipRect);
upArrow = new ArrowMenuItem(this);
upArrow.setUp(true);
upArrow.setFocusTraversable(false);
downArrow = new ArrowMenuItem(this);
downArrow.setUp(false);
downArrow.setFocusTraversable(false);
getChildren().add(itemsContainer);
getChildren().add(upArrow);
getChildren().add(downArrow);
initialize();
setUpBinds();
updateItems();
// RT-20197 add menuitems only on first show.
popupMenu.showingProperty().addListener(weakPopupShowingListener);
/*
** only add this if we're on an embedded
** platform that supports 5-button navigation
*/
if (Utils.isTwoLevelFocus()) {
new TwoLevelFocusPopupBehavior(this);
}
}
//For access from controls
public VBox getItemsContainer() {
return itemsContainer;
}
//For testing purpose only
int getCurrentFocusIndex() {
return currentFocusedIndex;
}
//For testing purpose only
void setCurrentFocusedIndex(int index) {
if (index < itemsContainer.getChildren().size()) {
currentFocusedIndex = index;
}
}
private void updateItems() {
if (itemsDirty) {
updateVisualItems();
itemsDirty = false;
}
}
private void computeVisualMetrics() {
maxRightWidth = 0;
maxLabelWidth = 0;
maxRowHeight = 0;
maxGraphicWidth = 0;
maxLeftWidth = 0;
for (int i = 0; i < itemsContainer.getChildren().size(); i++) {
Node child = itemsContainer.getChildren().get(i);
if (child instanceof MenuItemContainer) {
final MenuItemContainer menuItemContainer = (MenuItemContainer)itemsContainer.getChildren().get(i);
if (! menuItemContainer.isVisible()) continue;
double alt = -1;
Node n = menuItemContainer.left;
if (n != null) {
if (n.getContentBias() == Orientation.VERTICAL) { // width depends on height
alt = snapSizeY(n.prefHeight(-1));
} else alt = -1;
maxLeftWidth = Math.max(maxLeftWidth, snapSizeX(n.prefWidth(alt)));
maxRowHeight = Math.max(maxRowHeight, n.prefHeight(-1));
}
n = menuItemContainer.graphic;
if (n != null) {
if (n.getContentBias() == Orientation.VERTICAL) { // width depends on height
alt = snapSizeY(n.prefHeight(-1));
} else alt = -1;
maxGraphicWidth = Math.max(maxGraphicWidth, snapSizeX(n.prefWidth(alt)));
maxRowHeight = Math.max(maxRowHeight, n.prefHeight(-1));
}
n = menuItemContainer.label;
if (n != null) {
if (n.getContentBias() == Orientation.VERTICAL) {
alt = snapSizeY(n.prefHeight(-1));
} else alt = -1;
maxLabelWidth = Math.max(maxLabelWidth, snapSizeX(n.prefWidth(alt)));
maxRowHeight = Math.max(maxRowHeight, n.prefHeight(-1));
}
n = menuItemContainer.right;
if (n != null) {
if (n.getContentBias() == Orientation.VERTICAL) { // width depends on height
alt = snapSizeY(n.prefHeight(-1));
} else alt = -1;
maxRightWidth = Math.max(maxRightWidth, snapSizeX(n.prefWidth(alt)));
maxRowHeight = Math.max(maxRowHeight, n.prefHeight(-1));
}
}
}
// Fix for RT-38838.
// This fixes the issue where CSS is applied to a menu after it has been
// showing, resulting in its bounds changing. In this case, we need to
// shift the submenu such that it is properly aligned with its parent menu.
//
// To do this, we must firstly determine if the open submenu is shifted
// horizontally to appear on the other side of this menu, as this is the
// only situation where shifting has to happen. If so, we need to check
// if we should shift the submenu due to changes in width.
//
// We need to get the parent menu of this contextMenu, so that we only
// modify the X value in the following conditions:
// 1) There exists a parent menu
// 2) The parent menu is in the correct position (i.e. to the left of this
// menu in normal LTR systems).
final double newWidth = maxRightWidth + maxLabelWidth + maxGraphicWidth + maxLeftWidth;
Window ownerWindow = contextMenu.getOwnerWindow();
if (ownerWindow instanceof ContextMenu) {
if (contextMenu.getX() < ownerWindow.getX()) {
if (oldWidth != newWidth) {
contextMenu.setX(contextMenu.getX() + oldWidth - newWidth);
}
}
}
oldWidth = newWidth;
}
private void updateVisualItems() {
ObservableList itemsContainerChilder = itemsContainer.getChildren();
disposeVisualItems();
for (int row = 0; row < getItems().size(); row++) {
final MenuItem item = getItems().get(row);
if (item instanceof CustomMenuItem && ((CustomMenuItem) item).getContent() == null) {
continue;
}
if (item instanceof SeparatorMenuItem) {
// we don't want the hover highlight for separators, so for
// now this is the simplest approach - just remove the
// background entirely. This may cause issues if people
// intend to style the background differently.
Node node = ((CustomMenuItem) item).getContent();
node.visibleProperty().bind(item.visibleProperty());
itemsContainerChilder.add(node);
// Add the (separator) menu item to properties map of this node.
// Special casing this for separator :
// This allows associating this container with SeparatorMenuItem.
node.getProperties().put(MenuItem.class, item);
} else {
MenuItemContainer menuItemContainer = new MenuItemContainer(item);
menuItemContainer.visibleProperty().bind(item.visibleProperty());
itemsContainerChilder.add(menuItemContainer);
}
}
// Add the Menu to properties map of this skin. Used by QA for testing
// This enables associating a parent menu for this skin showing menu items.
if (getItems().size() > 0) {
final MenuItem item = getItems().get(0);
getProperties().put(Menu.class, item.getParentMenu());
}
// RT-36513 made this applyCss(). Modified by RT-36995 to NodeHelper.reapplyCSS()
NodeHelper.reapplyCSS(this);
}
private void disposeVisualItems() {
// clean up itemsContainer
ObservableList itemsContainerChilder = itemsContainer.getChildren();
for (int i = 0, max = itemsContainerChilder.size(); i < max; i++) {
Node n = itemsContainerChilder.get(i);
if (n instanceof MenuItemContainer) {
MenuItemContainer container = (MenuItemContainer) n;
container.visibleProperty().unbind();
container.dispose();
}
}
itemsContainerChilder.clear();
}
/**
* Can be called by Skins when they need to clean up the content of any
* ContextMenu instances they might have created. This ensures that contents
* of submenus if any, also get cleaned up.
*/
public void dispose() {
disposeBinds();
disposeVisualItems();
disposeContextMenu(submenu);
submenu = null;
openSubmenu = null;
selectedBackground = null;
if (contextMenu != null) {
contextMenu.getItems().clear();
contextMenu = null;
}
}
public void disposeContextMenu(ContextMenu menu) {
if (menu == null) return;
Skin> skin = menu.getSkin();
if (skin == null) return;
menu.showingProperty().removeListener(subMenuShowingListener);
ContextMenuContent cmContent = (ContextMenuContent)skin.getNode();
if (cmContent == null) return;
cmContent.dispose(); // recursive call to dispose submenus.
}
@Override protected void layoutChildren() {
if (itemsContainer.getChildren().size() == 0) return;
final double x = snappedLeftInset();
final double y = snappedTopInset();
final double w = getWidth() - x - snappedRightInset();
final double h = getHeight() - y - snappedBottomInset();
final double contentHeight = snapSizeY(getContentHeight()); // itemsContainer.prefHeight(-1);
itemsContainer.resize(w,contentHeight);
itemsContainer.relocate(x, y);
if (contentHeight < Math.abs(ty)) {
/*
** This condition occurs when context menu with large number of items
** are replaced by smaller number of items.
** Scroll to the top to display the context menu items.
*/
scroll(Math.abs(ty));
}
if (isFirstShow && ty == 0) {
upArrow.setVisible(false);
isFirstShow = false;
} else {
upArrow.setVisible(ty < y && ty < 0);
}
downArrow.setVisible(ty + contentHeight > (y + h));
clipRect.setX(0);
clipRect.setY(0);
clipRect.setWidth(w);
clipRect.setHeight(h);
if (upArrow.isVisible()) {
final double prefHeight = snapSizeY(upArrow.prefHeight(-1));
clipRect.setHeight(snapSizeY(clipRect.getHeight() - prefHeight));
clipRect.setY(snapSizeY(clipRect.getY()) + prefHeight);
upArrow.resize(snapSizeX(upArrow.prefWidth(-1)), prefHeight);
positionInArea(upArrow, x, y, w, prefHeight, /*baseline ignored*/0,
HPos.CENTER, VPos.CENTER);
}
if (downArrow.isVisible()) {
final double prefHeight = snapSizeY(downArrow.prefHeight(-1));
clipRect.setHeight(snapSizeY(clipRect.getHeight()) - prefHeight);
downArrow.resize(snapSizeX(downArrow.prefWidth(-1)), prefHeight);
positionInArea(downArrow, x, (y + h - prefHeight), w, prefHeight, /*baseline ignored*/0,
HPos.CENTER, VPos.CENTER);
}
}
@Override protected double computePrefWidth(double height) {
computeVisualMetrics();
double prefWidth = 0;
if (itemsContainer.getChildren().size() == 0) return 0;
for (Node n : itemsContainer.getChildren()) {
if (! n.isVisible()) continue;
prefWidth = Math.max(prefWidth, snapSizeX(n.prefWidth(-1)));
}
return snappedLeftInset() + snapSizeX(prefWidth) + snappedRightInset();
}
@Override protected double computePrefHeight(double width) {
if (itemsContainer.getChildren().size() == 0) return 0;
final double screenHeight = getScreenHeight();
final double contentHeight = getContentHeight(); // itemsContainer.prefHeight(width);
double totalHeight = snappedTopInset() + snapSizeY(contentHeight) + snappedBottomInset();
// the pref height of this menu is the smaller value of the
// actual pref height and the height of the screens _visual_ bounds.
double prefHeight = (screenHeight <= 0) ? (totalHeight) : (Math.min(totalHeight, screenHeight));
return prefHeight;
}
@Override protected double computeMinHeight(double width) {
return 0.0;
}
@Override protected double computeMaxHeight(double height) {
return getScreenHeight();
}
private double getScreenHeight() {
if (contextMenu == null || contextMenu.getOwnerWindow() == null ||
contextMenu.getOwnerWindow().getScene() == null) {
return -1;
}
return snapSizeY(com.sun.javafx.util.Utils.getScreen(
contextMenu.getOwnerWindow().getScene().getRoot()).getVisualBounds().getHeight());
}
private double getContentHeight() {
double h = 0.0d;
for (Node i : itemsContainer.getChildren()) {
if (i.isVisible()) {
h += snapSizeY(i.prefHeight(-1));
}
}
return h;
}
// This handles shifting ty when doing keyboard navigation.
private void ensureFocusedMenuItemIsVisible(Node node) {
if (node == null) return;
final Bounds nodeBounds = node.getBoundsInParent();
final Bounds clipBounds = clipRect.getBoundsInParent();
if (nodeBounds.getMaxY() >= clipBounds.getMaxY()) {
// this is for moving down the menu
scroll(-nodeBounds.getMaxY() + clipBounds.getMaxY());
} else if (nodeBounds.getMinY() <= clipBounds.getMinY()) {
// this is for moving up the menu
scroll(-nodeBounds.getMinY() + clipBounds.getMinY());
}
}
protected ObservableList