changeset 1064:8ba525f169f9

ContextMenu for TextInputControl.
author Kinsley Wong
date Fri, 18 May 2012 10:45:06 -0700
parents eb89f9cc63e6
children bd51a39469d7
files javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextAreaBehavior.java javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextFieldBehavior.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ContextMenuSkin.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextAreaSkin.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextFieldSkin.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextInputContextMenuContent.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextInputControlSkin.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/caspian/caspian.css
diffstat 8 files changed, 258 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextAreaBehavior.java	Fri May 18 10:16:11 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextAreaBehavior.java	Fri May 18 10:45:06 2012 -0700
@@ -146,21 +146,6 @@
                 }
             }
         });
-
-
-        if (textArea.getOnTouchStationary() == null) {
-            textArea.setOnTouchStationary(new EventHandler<TouchEvent>() {
-                @Override public void handle(TouchEvent event) {
-                    ContextMenu menu = textArea.getContextMenu();
-                    if (menu != null &&
-                        skin.showContextMenu(menu, event.getTouchPoint().getScreenX(),
-                                        event.getTouchPoint().getScreenY(), false)) {
-                        deferClick = false;
-                        event.consume();
-                    }
-                }
-            });
-        }
     }
 
     // An unholy back-reference!
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextFieldBehavior.java	Fri May 18 10:16:11 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextFieldBehavior.java	Fri May 18 10:45:06 2012 -0700
@@ -105,20 +105,6 @@
                 handleFocusChange();
             }
         });
-
-        if (textField.getOnTouchStationary() == null) {
-            textField.setOnTouchStationary(new EventHandler<TouchEvent>() {
-                @Override public void handle(TouchEvent event) {
-                    ContextMenu menu = textField.getContextMenu();
-                    if (menu != null &&
-                        skin.showContextMenu(menu, event.getTouchPoint().getScreenX(),
-                                        event.getTouchPoint().getScreenY(), false)) {
-                        deferClick = false;
-                        event.consume();
-                    }
-                }
-            });
-        }
     }
 
     private void handleFocusChange() {
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ContextMenuSkin.java	Fri May 18 10:16:11 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ContextMenuSkin.java	Fri May 18 10:45:06 2012 -0700
@@ -56,12 +56,26 @@
         // When a contextMenu is shown, requestFocus on its content to enable
         // keyboard navigation.
         popupMenu.addEventHandler(Menu.ON_SHOWN, new EventHandler() {
-            @Override public void handle(Event event) {
-                 ContextMenuContent cmContent = (ContextMenuContent)popupMenu.getSkin().getNode();
-                if (cmContent != null) cmContent.requestFocus();
-            }
-        });
-        root = new ContextMenuContent(popupMenu);
+             @Override public void handle(Event event) {
+                if (popupMenu.getStyleClass().contains("text-input-context-menu")) {
+                    TextInputContextMenuContent cmContent = (TextInputContextMenuContent)popupMenu.getSkin().getNode();
+                    if (cmContent != null) {
+                        cmContent.requestFocus();
+                    }                    
+                } else {
+                    ContextMenuContent cmContent = (ContextMenuContent)popupMenu.getSkin().getNode();
+                    if (cmContent != null) {
+                        cmContent.requestFocus();
+                    }
+                }
+             }
+         });
+
+        if (popupMenu.getStyleClass().contains("text-input-context-menu")) {
+            root = new TextInputContextMenuContent(popupMenu);
+        } else {
+            root = new ContextMenuContent(popupMenu);
+        }
         root.idProperty().bind(popupMenu.idProperty());
         root.styleProperty().bind(popupMenu.styleProperty());
         root.getStyleClass().addAll(popupMenu.getStyleClass()); // TODO needs to handle updates
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextAreaSkin.java	Fri May 18 10:16:11 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextAreaSkin.java	Fri May 18 10:45:06 2012 -0700
@@ -69,6 +69,7 @@
 import com.sun.javafx.PlatformUtil;
 import com.sun.javafx.scene.control.behavior.TextAreaBehavior;
 import com.sun.javafx.scene.text.HitInfo;
+import javafx.stage.Screen;
 
 /**
  * Text area skin.
@@ -431,10 +432,10 @@
         scrollPane.setFitToWidth(textArea.isWrapText());
         scrollPane.setContent(contentView);
         getChildren().add(scrollPane);
-
-        // Workaround
+        
         if (textArea.getContextMenu() != null) {
-            scrollPane.setContextMenu(textArea.getContextMenu());
+            // Set to false so we can get the Anchor node in TextInputContextMenuContent.
+            textArea.getContextMenu().setImpl_showRelativeToWindow(false);   
         }
 
         // Add selection
@@ -963,7 +964,28 @@
             x = p.getX();
             y = p.getY();
         }
-        return super.showContextMenu(menu, x, y, isKeyboardTrigger);
+        
+        double menuWidth = menu.prefWidth(-1);
+        double menuX = x - menuWidth/2;
+        Screen currentScreen = com.sun.javafx.Utils.getScreenForPoint(0, 0);
+        double maxWidth = currentScreen.getVisualBounds().getWidth();
+                        
+        double windowX = getScene().getWindow().getX() + getScene().getX();
+        double sceneX = x - windowX;
+
+        if (menuX < 0) {
+            getProperties().put("CONTEXT_MENU_SCREEN_X", x);
+            getProperties().put("CONTEXT_MENU_SCENE_X", sceneX);
+            return super.showContextMenu(menu, 0, y, isKeyboardTrigger);            
+        } else if (x + menu.prefWidth(-1) > maxWidth) {            
+            double leftOver = menuWidth - (maxWidth - x);
+            getProperties().put("CONTEXT_MENU_SCREEN_X", x);
+            getProperties().put("CONTEXT_MENU_SCENE_X", sceneX);
+            return super.showContextMenu(menu, x - leftOver, y, isKeyboardTrigger);
+        }
+        getProperties().put("CONTEXT_MENU_SCREEN_X", 0);
+        getProperties().put("CONTEXT_MENU_SCENE_X", 0);    
+        return super.showContextMenu(menu, menuX, y, isKeyboardTrigger);
     }
 
     @Override public void scrollCharacterToVisible(final int index) {
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextFieldSkin.java	Fri May 18 10:16:11 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextFieldSkin.java	Fri May 18 10:45:06 2012 -0700
@@ -64,6 +64,7 @@
 import com.sun.javafx.scene.control.behavior.TextFieldBehavior;
 import com.sun.javafx.scene.text.HitInfo;
 import com.sun.javafx.tk.FontMetrics;
+import javafx.stage.Screen;
 
 /**
  * Text field skin.
@@ -154,7 +155,27 @@
             x = p.getX();
             y = p.getY();
         }
-        return super.showContextMenu(menu, x, y, isKeyboardTrigger);
+        double menuWidth = menu.prefWidth(-1);
+        double menuX = x - menuWidth/2;
+        Screen currentScreen = com.sun.javafx.Utils.getScreenForPoint(0, 0);
+        double maxWidth = currentScreen.getVisualBounds().getWidth();
+                        
+        double windowX = getScene().getWindow().getX() + getScene().getX();
+        double sceneX = x - windowX;
+
+        if (menuX < 0) {
+            getProperties().put("CONTEXT_MENU_SCREEN_X", x);
+            getProperties().put("CONTEXT_MENU_SCENE_X", sceneX);
+            return super.showContextMenu(menu, 0, y, isKeyboardTrigger);            
+        } else if (x + menu.prefWidth(-1) > maxWidth) {            
+            double leftOver = menuWidth - (maxWidth - x);
+            getProperties().put("CONTEXT_MENU_SCREEN_X", x);
+            getProperties().put("CONTEXT_MENU_SCENE_X", sceneX);
+            return super.showContextMenu(menu, x - leftOver, y, isKeyboardTrigger);
+        }
+        getProperties().put("CONTEXT_MENU_SCREEN_X", 0);
+        getProperties().put("CONTEXT_MENU_SCENE_X", 0);    
+        return super.showContextMenu(menu, menuX, y, isKeyboardTrigger);        
     }
 
     /**
@@ -363,8 +384,11 @@
             }
         });
 
-
-
+        if (textField.getContextMenu() != null) {
+            // Set to false so we can get the Anchor node in TextInputContextMenuContent.
+            textField.getContextMenu().setImpl_showRelativeToWindow(false);   
+        }
+        
         if (PlatformUtil.isEmbedded()) {
             EventHandler<MouseEvent> handlePressHandler = new EventHandler<MouseEvent>() {
                 @Override public void handle(MouseEvent e) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextInputContextMenuContent.java	Fri May 18 10:45:06 2012 -0700
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2012, 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.skin;
+
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.geometry.HPos;
+import javafx.geometry.VPos;
+import javafx.scene.control.*;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.StackPane;
+
+public class TextInputContextMenuContent extends StackPane {
+
+    private ContextMenu contextMenu;
+    private StackPane pointer;
+    private HBox menuBox;
+
+    public TextInputContextMenuContent(final ContextMenu popupMenu) {
+        this.contextMenu = popupMenu;
+        this.menuBox = new HBox();
+        this.pointer = new StackPane();
+        pointer.getStyleClass().add("pointer");
+
+        updateMenuItemContainer();
+        getChildren().addAll(pointer, menuBox);
+
+        contextMenu.ownerNodeProperty().addListener(new InvalidationListener() {
+            @Override public void invalidated(Observable arg0) {
+                if (contextMenu.getOwnerNode() instanceof TextArea) {
+                    TextAreaSkin tas = (TextAreaSkin)((TextArea)contextMenu.getOwnerNode()).getSkin();
+                    tas.getProperties().addListener(new InvalidationListener() {
+                        @Override public void invalidated(Observable arg0) {
+                            requestLayout();
+                        }
+                    });
+                } else if (contextMenu.getOwnerNode() instanceof TextField) {
+                    TextFieldSkin tfs = (TextFieldSkin)((TextField)contextMenu.getOwnerNode()).getSkin();
+                    tfs.getProperties().addListener(new InvalidationListener() {
+                        @Override public void invalidated(Observable arg0) {
+                            requestLayout();
+                        }
+                    });
+                }
+            }
+        });
+    }
+
+    private void updateMenuItemContainer() {
+        menuBox.getChildren().clear();
+        for (MenuItem item: contextMenu.getItems()) {
+            MenuItemContainer menuItemContainer = new MenuItemContainer(item);
+            menuItemContainer.visibleProperty().bind(item.visibleProperty());
+            menuBox.getChildren().add(menuItemContainer);
+        }
+    }
+
+    private void hideAllMenus(MenuItem item) {
+        contextMenu.hide();
+
+        Menu parentMenu;
+        while ((parentMenu = item.getParentMenu()) != null) {
+            parentMenu.hide();
+            item = parentMenu;
+        }
+        if (parentMenu == null && item.getParentPopup() != null) {
+            item.getParentPopup().hide();
+        }
+    }
+
+    @Override protected double computePrefHeight(double width) {
+        double top = snapSpace(getInsets().getTop());
+        double bottom = snapSpace(getInsets().getBottom());
+        double pointerHeight = snapSize(pointer.prefHeight(width));
+        double menuBoxHeight = snapSize(menuBox.prefHeight(width));
+
+        return top + pointerHeight + menuBoxHeight + bottom;
+    }
+
+    @Override protected void layoutChildren() {
+        double left = snapSpace(getInsets().getLeft());
+        double right = snapSpace(getInsets().getRight());
+        double top = snapSpace(getInsets().getTop());        
+        double width = snapSize(getWidth() - (left + right));
+        double pointerWidth = snapSize(Utils.boundedSize(pointer.prefWidth(-1), pointer.minWidth(-1), pointer.maxWidth(-1)));
+        double pointerHeight = snapSize(Utils.boundedSize(pointer.prefWidth(-1), pointer.minWidth(-1), pointer.maxWidth(-1)));
+        double menuBoxWidth = snapSize(Utils.boundedSize(menuBox.prefWidth(-1), menuBox.minWidth(-1), menuBox.maxWidth(-1)));
+        double menuBoxHeight = snapSize(Utils.boundedSize(menuBox.prefWidth(-1), menuBox.minWidth(-1), menuBox.maxWidth(-1)));
+        double sceneX = 0;
+        double screenX = 0;
+        double pointerX = 0;
+
+        if (contextMenu.getOwnerNode() instanceof TextArea) {
+            TextArea ta = (TextArea)contextMenu.getOwnerNode();
+            TextAreaSkin tas = (TextAreaSkin)ta.getSkin();
+            sceneX = Double.valueOf(tas.getProperties().get("CONTEXT_MENU_SCENE_X").toString());
+            screenX = Double.valueOf(tas.getProperties().get("CONTEXT_MENU_SCREEN_X").toString());
+            tas.getProperties().clear();
+        } else if (contextMenu.getOwnerNode() instanceof TextField) {
+            TextField tf = (TextField)contextMenu.getOwnerNode();
+            TextFieldSkin tfs = (TextFieldSkin)tf.getSkin();
+            sceneX = Double.valueOf(tfs.getProperties().get("CONTEXT_MENU_SCENE_X").toString());
+            screenX = Double.valueOf(tfs.getProperties().get("CONTEXT_MENU_SCREEN_X").toString());
+            tfs.getProperties().clear();
+        }
+        if (sceneX == 0) {
+            pointerX = width/2;
+        } else {
+            pointerX = (screenX - sceneX - contextMenu.getX()) + sceneX;
+        }
+
+        pointer.resize(pointerWidth, pointerHeight);
+        positionInArea(pointer, pointerX, top, pointerWidth, pointerHeight, 0, HPos.CENTER, VPos.CENTER);
+        menuBox.resize(menuBoxWidth, menuBoxHeight);
+        positionInArea(menuBox, left, top + pointerHeight, menuBoxWidth, menuBoxHeight, 0, HPos.CENTER, VPos.CENTER);
+    }
+
+    class MenuItemContainer extends Button {
+        private MenuItem item;
+
+        public MenuItemContainer(MenuItem item){
+            getStyleClass().addAll(item.getStyleClass());
+            setId(item.getId());
+            this.item = item;
+            setText(item.getText());
+            setStyle(item.getStyle());
+
+            // bind to text property in menu item
+            textProperty().bind(item.textProperty());
+        }
+
+        public MenuItem getItem() {
+            return item;
+        }
+
+        @Override public void fire() {
+            hideAllMenus(item);
+            super.fire();
+        }
+    }
+}
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextInputControlSkin.java	Fri May 18 10:16:11 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/TextInputControlSkin.java	Fri May 18 10:45:06 2012 -0700
@@ -376,8 +376,9 @@
             final MenuItem selectMI = new ContextMenuItem("SelectAll");
 
             final ContextMenu cm = new ContextMenu(undoMI, redoMI, cutMI, copyMI, pasteMI, deleteMI,
-                                                   new SeparatorMenuItem(), selectMI);
-
+                                             new SeparatorMenuItem(), selectMI);
+                     
+            cm.getStyleClass().add("text-input-context-menu");
             cm.setOnShowing(new EventHandler<WindowEvent>() {
                 public void handle(WindowEvent e) {
                     boolean hasSelection = (textInput.getSelection().getLength() > 0);
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/caspian/caspian.css	Fri May 18 10:16:11 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/caspian/caspian.css	Fri May 18 10:45:06 2012 -0700
@@ -794,6 +794,24 @@
     -fx-stroke: -fx-text-fill;
 } 
 
+.context-menu .text-input-context-menu {
+    -fx-background-color: transparent;
+    -fx-background-radius: 0;
+    -fx-padding: 0;
+}
+
+.context-menu .text-input-context-menu .button {
+    -fx-background-radius: 0;   
+    -fx-background-color: derive(-fx-color,-40%);
+}
+
+.context-menu .text-input-context-menu .pointer {    
+    -fx-background-color: derive(-fx-color,-40%);    
+    -fx-padding: 6px;
+    -fx-shape: "M 6 0 L 12 12 L 0 12 z";
+    -fx-scale-shape: true;
+}
+
 /*******************************************************************************
  *                                                                             *
  * Menu                                                                        *