1 /* 2 * Copyright (c) 2012, 2014, 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.skin; 27 28 import java.util.Map; 29 import javafx.beans.InvalidationListener; 30 import javafx.beans.Observable; 31 import javafx.collections.ListChangeListener; 32 import javafx.event.ActionEvent; 33 import javafx.event.Event; 34 import javafx.geometry.HPos; 35 import javafx.geometry.VPos; 36 import javafx.scene.control.*; 37 import javafx.scene.layout.HBox; 38 import javafx.scene.layout.StackPane; 39 40 /** 41 * The embedded context menu for a text input control. 42 * The menu will be displayed horizontally underneath the cursor 43 * with the available text editing options i.e. cut, copy, paste, select all. 44 * 45 */ 46 public class EmbeddedTextContextMenuContent extends StackPane { 47 48 private ContextMenu contextMenu; 49 private StackPane pointer; 50 private HBox menuBox; 51 52 public EmbeddedTextContextMenuContent(final ContextMenu popupMenu) { 53 this.contextMenu = popupMenu; 54 this.menuBox = new HBox(); 55 this.pointer = new StackPane(); 56 pointer.getStyleClass().add("pointer"); 57 58 updateMenuItemContainer(); 59 getChildren().addAll(pointer, menuBox); 60 61 contextMenu.ownerNodeProperty().addListener(arg0 -> { 62 if (contextMenu.getOwnerNode() instanceof TextArea) { 63 TextAreaSkin tas = (TextAreaSkin)((TextArea)contextMenu.getOwnerNode()).getSkin(); 64 tas.getSkinnable().getProperties().addListener(new InvalidationListener() { 65 @Override public void invalidated(Observable arg0) { 66 requestLayout(); 67 } 68 }); 69 } else if (contextMenu.getOwnerNode() instanceof TextField) { 70 TextFieldSkin tfs = (TextFieldSkin)((TextField)contextMenu.getOwnerNode()).getSkin(); 71 tfs.getSkinnable().getProperties().addListener(new InvalidationListener() { 72 @Override public void invalidated(Observable arg0) { 73 requestLayout(); 74 } 75 }); 76 } 77 }); 78 79 contextMenu.getItems().addListener((ListChangeListener<MenuItem>) c -> { 80 // Listener to items in PopupMenu to update items in PopupMenuContent 81 updateMenuItemContainer(); 82 }); 83 } 84 85 private void updateMenuItemContainer() { 86 menuBox.getChildren().clear(); 87 for (MenuItem item: contextMenu.getItems()) { 88 MenuItemContainer menuItemContainer = new MenuItemContainer(item); 89 menuItemContainer.visibleProperty().bind(item.visibleProperty()); 90 menuBox.getChildren().add(menuItemContainer); 91 } 92 } 93 94 private void hideAllMenus(MenuItem item) { 95 contextMenu.hide(); 96 97 Menu parentMenu; 98 while ((parentMenu = item.getParentMenu()) != null) { 99 parentMenu.hide(); 100 item = parentMenu; 101 } 102 if (parentMenu == null && item.getParentPopup() != null) { 103 item.getParentPopup().hide(); 104 } 105 } 106 107 @Override protected double computePrefHeight(double width) { 108 final double pointerHeight = snapSize(pointer.prefHeight(width)); 109 final double menuBoxHeight = snapSize(menuBox.prefHeight(width)); 110 return snappedTopInset() + pointerHeight + menuBoxHeight + snappedBottomInset(); 111 } 112 113 @Override protected double computePrefWidth(double height) { 114 final double menuBoxWidth = snapSize(menuBox.prefWidth(height)); 115 return snappedLeftInset() + menuBoxWidth + snappedRightInset(); 116 } 117 118 @Override protected void layoutChildren() { 119 final double left = snappedLeftInset(); 120 final double right = snappedRightInset(); 121 final double top = snappedTopInset(); 122 final double width = getWidth() - (left + right); 123 final double pointerWidth = snapSize(Utils.boundedSize(pointer.prefWidth(-1), pointer.minWidth(-1), pointer.maxWidth(-1))); 124 final double pointerHeight = snapSize(Utils.boundedSize(pointer.prefWidth(-1), pointer.minWidth(-1), pointer.maxWidth(-1))); 125 final double menuBoxWidth = snapSize(Utils.boundedSize(menuBox.prefWidth(-1), menuBox.minWidth(-1), menuBox.maxWidth(-1))); 126 final double menuBoxHeight = snapSize(Utils.boundedSize(menuBox.prefWidth(-1), menuBox.minWidth(-1), menuBox.maxWidth(-1))); 127 double sceneX = 0; 128 double screenX = 0; 129 double pointerX = 0; 130 131 // Get the positions of the cursor from the TextArea/TextField and draw the arrow underneath it. 132 Map<Object,Object> properties = null; 133 if (contextMenu.getOwnerNode() instanceof TextArea) { 134 properties = ((TextArea)contextMenu.getOwnerNode()).getProperties(); 135 } else if (contextMenu.getOwnerNode() instanceof TextField) { 136 properties = ((TextField)contextMenu.getOwnerNode()).getProperties(); 137 } 138 139 if (properties != null) { 140 if (properties.containsKey("CONTEXT_MENU_SCENE_X")) { 141 sceneX = Double.valueOf(properties.get("CONTEXT_MENU_SCENE_X").toString()); 142 properties.remove("CONTEXT_MENU_SCENE_X"); 143 } 144 145 if (properties.containsKey("CONTEXT_MENU_SCREEN_X")) { 146 screenX = Double.valueOf(properties.get("CONTEXT_MENU_SCREEN_X").toString()); 147 properties.remove("CONTEXT_MENU_SCREEN_X"); 148 } 149 } 150 151 if (sceneX == 0) { 152 pointerX = width/2; 153 } else { 154 pointerX = (screenX - sceneX - contextMenu.getX()) + sceneX; 155 } 156 157 pointer.resize(pointerWidth, pointerHeight); 158 positionInArea(pointer, pointerX, top, pointerWidth, pointerHeight, 0, HPos.CENTER, VPos.CENTER); 159 menuBox.resize(menuBoxWidth, menuBoxHeight); 160 positionInArea(menuBox, left, top + pointerHeight, menuBoxWidth, menuBoxHeight, 0, HPos.CENTER, VPos.CENTER); 161 } 162 163 class MenuItemContainer extends Button { 164 private MenuItem item; 165 166 public MenuItemContainer(MenuItem item){ 167 getStyleClass().addAll(item.getStyleClass()); 168 setId(item.getId()); 169 this.item = item; 170 setText(item.getText()); 171 setStyle(item.getStyle()); 172 173 // bind to text property in menu item 174 textProperty().bind(item.textProperty()); 175 } 176 177 public MenuItem getItem() { 178 return item; 179 } 180 181 @Override public void fire() { 182 Event.fireEvent(item, new ActionEvent()); 183 if (Boolean.TRUE.equals((Boolean)item.getProperties().get("refreshMenu"))) { 184 //refreshMenu(); 185 } else { 186 hideAllMenus(item); 187 } 188 } 189 } 190 }