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 }