1 /* 2 * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.scene.control; 27 28 import java.util.Map; 29 30 import com.sun.javafx.scene.control.skin.*; 31 import javafx.beans.InvalidationListener; 32 import javafx.beans.Observable; 33 import javafx.collections.ListChangeListener; 34 import javafx.event.ActionEvent; 35 import javafx.event.Event; 36 import javafx.geometry.HPos; 37 import javafx.geometry.VPos; 38 import javafx.scene.control.*; 39 import javafx.scene.control.skin.TextAreaSkin; 40 import javafx.scene.control.skin.TextFieldSkin; 41 import javafx.scene.layout.HBox; 42 import javafx.scene.layout.StackPane; 43 44 /** 45 * The embedded context menu for a text input control. 46 * The menu will be displayed horizontally underneath the cursor 47 * with the available text editing options i.e. cut, copy, paste, select all. 48 * 49 */ 50 public class EmbeddedTextContextMenuContent extends StackPane { 51 52 private ContextMenu contextMenu; 53 private StackPane pointer; 54 private HBox menuBox; 55 56 public EmbeddedTextContextMenuContent(final ContextMenu popupMenu) { 57 this.contextMenu = popupMenu; 58 this.menuBox = new HBox(); 59 this.pointer = new StackPane(); 60 pointer.getStyleClass().add("pointer"); 61 62 updateMenuItemContainer(); 63 getChildren().addAll(pointer, menuBox); 64 65 contextMenu.ownerNodeProperty().addListener(arg0 -> { 66 if (contextMenu.getOwnerNode() instanceof TextArea) { 67 TextAreaSkin tas = (TextAreaSkin)((TextArea)contextMenu.getOwnerNode()).getSkin(); 68 tas.getSkinnable().getProperties().addListener(new InvalidationListener() { 69 @Override public void invalidated(Observable arg0) { 70 requestLayout(); 71 } 72 }); 73 } else if (contextMenu.getOwnerNode() instanceof TextField) { 74 TextFieldSkin tfs = (TextFieldSkin)((TextField)contextMenu.getOwnerNode()).getSkin(); 75 tfs.getSkinnable().getProperties().addListener(new InvalidationListener() { 76 @Override public void invalidated(Observable arg0) { 77 requestLayout(); 78 } 79 }); 80 } 81 }); 82 83 contextMenu.getItems().addListener((ListChangeListener<MenuItem>) c -> { 84 // Listener to items in PopupMenu to update items in PopupMenuContent 85 updateMenuItemContainer(); 86 }); 87 } 88 89 private void updateMenuItemContainer() { 90 menuBox.getChildren().clear(); 91 for (MenuItem item: contextMenu.getItems()) { 92 MenuItemContainer menuItemContainer = new MenuItemContainer(item); 93 menuItemContainer.visibleProperty().bind(item.visibleProperty()); 94 menuBox.getChildren().add(menuItemContainer); 95 } 96 } 97 98 private void hideAllMenus(MenuItem item) { 99 contextMenu.hide(); 100 101 Menu parentMenu; 102 while ((parentMenu = item.getParentMenu()) != null) { 103 parentMenu.hide(); 104 item = parentMenu; 105 } 106 if (parentMenu == null && item.getParentPopup() != null) { 107 item.getParentPopup().hide(); 108 } 109 } 110 111 @Override protected double computePrefHeight(double width) { 112 final double pointerHeight = snapSizeY(pointer.prefHeight(width)); 113 final double menuBoxHeight = snapSizeY(menuBox.prefHeight(width)); 114 return snappedTopInset() + pointerHeight + menuBoxHeight + snappedBottomInset(); 115 } 116 117 @Override protected double computePrefWidth(double height) { 118 final double menuBoxWidth = snapSizeX(menuBox.prefWidth(height)); 119 return snappedLeftInset() + menuBoxWidth + snappedRightInset(); 120 } 121 122 @Override protected void layoutChildren() { 123 final double left = snappedLeftInset(); 124 final double right = snappedRightInset(); 125 final double top = snappedTopInset(); 126 final double width = getWidth() - (left + right); 127 final double pointerWidth = snapSizeX(Utils.boundedSize(pointer.prefWidth(-1), pointer.minWidth(-1), pointer.maxWidth(-1))); 128 final double pointerHeight = snapSizeY(Utils.boundedSize(pointer.prefWidth(-1), pointer.minWidth(-1), pointer.maxWidth(-1))); 129 final double menuBoxWidth = snapSizeX(Utils.boundedSize(menuBox.prefWidth(-1), menuBox.minWidth(-1), menuBox.maxWidth(-1))); 130 final double menuBoxHeight = snapSizeY(Utils.boundedSize(menuBox.prefWidth(-1), menuBox.minWidth(-1), menuBox.maxWidth(-1))); 131 double sceneX = 0; 132 double screenX = 0; 133 double pointerX = 0; 134 135 // Get the positions of the cursor from the TextArea/TextField and draw the arrow underneath it. 136 Map<Object,Object> properties = null; 137 if (contextMenu.getOwnerNode() instanceof TextArea) { 138 properties = ((TextArea)contextMenu.getOwnerNode()).getProperties(); 139 } else if (contextMenu.getOwnerNode() instanceof TextField) { 140 properties = ((TextField)contextMenu.getOwnerNode()).getProperties(); 141 } 142 143 if (properties != null) { 144 if (properties.containsKey("CONTEXT_MENU_SCENE_X")) { 145 sceneX = Double.valueOf(properties.get("CONTEXT_MENU_SCENE_X").toString()); 146 properties.remove("CONTEXT_MENU_SCENE_X"); 147 } 148 149 if (properties.containsKey("CONTEXT_MENU_SCREEN_X")) { 150 screenX = Double.valueOf(properties.get("CONTEXT_MENU_SCREEN_X").toString()); 151 properties.remove("CONTEXT_MENU_SCREEN_X"); 152 } 153 } 154 155 if (sceneX == 0) { 156 pointerX = width/2; 157 } else { 158 pointerX = (screenX - sceneX - contextMenu.getX()) + sceneX; 159 } 160 161 pointer.resize(pointerWidth, pointerHeight); 162 positionInArea(pointer, pointerX, top, pointerWidth, pointerHeight, 0, HPos.CENTER, VPos.CENTER); 163 menuBox.resize(menuBoxWidth, menuBoxHeight); 164 positionInArea(menuBox, left, top + pointerHeight, menuBoxWidth, menuBoxHeight, 0, HPos.CENTER, VPos.CENTER); 165 } 166 167 class MenuItemContainer extends Button { 168 private MenuItem item; 169 170 public MenuItemContainer(MenuItem item){ 171 getStyleClass().addAll(item.getStyleClass()); 172 setId(item.getId()); 173 this.item = item; 174 setText(item.getText()); 175 setStyle(item.getStyle()); 176 177 // bind to text property in menu item 178 textProperty().bind(item.textProperty()); 179 } 180 181 public MenuItem getItem() { 182 return item; 183 } 184 185 @Override public void fire() { 186 Event.fireEvent(item, new ActionEvent()); 187 if (!Boolean.TRUE.equals((Boolean)item.getProperties().get("refreshMenu"))) { 188 hideAllMenus(item); 189 } 190 } 191 } 192 }