1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 package com.oracle.javafx.scenebuilder.kit.editor.panel.inspector.editors;
  33 
  34 import com.oracle.javafx.scenebuilder.kit.editor.i18n.I18N;
  35 import com.oracle.javafx.scenebuilder.kit.metadata.property.ValuePropertyMetadata;
  36 import com.oracle.javafx.scenebuilder.kit.metadata.util.PrefixedValue;
  37 
  38 import java.util.Set;
  39 
  40 import javafx.event.ActionEvent;
  41 import javafx.event.EventHandler;
  42 import javafx.geometry.Pos;
  43 import javafx.scene.Node;
  44 import javafx.scene.control.Label;
  45 import javafx.scene.control.MenuItem;
  46 import javafx.scene.control.TextArea;
  47 import javafx.scene.control.TextField;
  48 import javafx.scene.control.TextInputControl;
  49 import javafx.scene.layout.HBox;
  50 import javafx.scene.layout.Priority;
  51 
  52 /**
  53  * String editor with I18n + multi-line handling.
  54  *
  55  *
  56  */
  57 public class I18nStringEditor extends PropertyEditor {
  58 
  59     private static final String PERCENT_STR = "%"; //NOI18N
  60     private TextInputControl textNode = new TextField();
  61     private HBox i18nHBox = null;
  62     private EventHandler<ActionEvent> valueListener;
  63     private final MenuItem i18nMenuItem = new MenuItem();
  64     private final String I18N_ON = I18N.getString("inspector.i18n.on");
  65     private final String I18N_OFF = I18N.getString("inspector.i18n.off");
  66     private final MenuItem multilineMenuItem = new MenuItem();
  67     private final String MULTI_LINE = I18N.getString("inspector.i18n.multiline");
  68     private final String SINGLE_LINE = I18N.getString("inspector.i18n.singleline");
  69     private boolean multiLineSupported = false;
  70     // Specific states
  71     private boolean i18nMode = false;
  72     private boolean multiLineMode = false;
  73 
  74     public I18nStringEditor(ValuePropertyMetadata propMeta, Set<Class<?>> selectedClasses, boolean multiLineSupported) {
  75         super(propMeta, selectedClasses);
  76         initialize(multiLineSupported);
  77     }
  78 
  79     private void initialize(boolean multiLineSupported) {
  80         this.multiLineSupported = multiLineSupported;
  81         valueListener = event -> {
  82             userUpdateValueProperty(getValue());
  83             textNode.selectAll();
  84         };
  85         setTextEditorBehavior(this, textNode, valueListener);
  86 
  87         getMenu().getItems().add(i18nMenuItem);
  88         getMenu().getItems().add(multilineMenuItem);
  89 
  90         i18nMenuItem.setOnAction(e -> {
  91             if (!i18nMode) {
  92                 setValue(new PrefixedValue(PrefixedValue.Type.RESOURCE_KEY, I18N.getString("inspector.i18n.dummykey")).toString());
  93             } else {
  94                 setValue(""); //NOI18N
  95             }
  96             I18nStringEditor.this.getCommitListener().handle(null);
  97             updateMenuItems();
  98         });
  99         multilineMenuItem.setOnAction(e -> {
 100             if (!multiLineMode) {
 101                 switchToTextArea();
 102             } else {
 103                 switchToTextField();
 104             }
 105             multiLineMode = !multiLineMode;
 106             updateMenuItems();
 107         });
 108     }
 109 
 110     @Override
 111     public Object getValue() {
 112         String val = textNode.getText();
 113         if (i18nMode) {
 114             val = new PrefixedValue(PrefixedValue.Type.RESOURCE_KEY, val).toString();
 115         } else {
 116             val = EditorUtils.getPlainString(val);
 117         }
 118         return val;
 119     }
 120 
 121     @Override
 122     public void setValue(Object value) {
 123         setValueGeneric(value);
 124         if (isSetValueDone()) {
 125             return;
 126         }
 127 
 128         if (value == null) {
 129             textNode.setText(null);
 130             return;
 131         }
 132         assert value instanceof String;
 133         String val = (String) value;
 134         PrefixedValue prefixedValue = new PrefixedValue(val);
 135         String suffix = prefixedValue.getSuffix();
 136 
 137         // Handle i18n
 138         if (prefixedValue.isResourceKey()) {
 139             if (!i18nMode) {
 140                 wrapInHBox();
 141                 i18nMode = true;
 142             }
 143         } else if (i18nMode) {
 144             // no percent + i18nMode
 145             unwrapHBox();
 146             i18nMode = false;
 147         }
 148 
 149         // Handle multi-line
 150         if (containsLineFeed(prefixedValue.toString())) {
 151             if (i18nMode) {
 152                 // multi-line + i18n ==> set as i18n only
 153                 multiLineMode = false;
 154                 switchToTextField();
 155             } else {
 156                 if (!multiLineMode) {
 157                     multiLineMode = true;
 158                     switchToTextArea();
 159                 }
 160             }
 161         } else {
 162             // no line feed
 163             if (multiLineMode) {
 164                 multiLineMode = false;
 165                 switchToTextField();
 166             }
 167         }
 168 
 169         if (i18nMode) {
 170             textNode.setText(suffix);
 171         } else {
 172             // We may have other special characters (@, $, ...) to display in the text field
 173             textNode.setText(prefixedValue.toString());
 174         }
 175         updateMenuItems();
 176     }
 177 
 178     public void reset(ValuePropertyMetadata propMeta, Set<Class<?>> selectedClasses, boolean multiLineSupported) {
 179         super.reset(propMeta, selectedClasses);
 180         this.multiLineSupported = multiLineSupported;
 181         textNode.setPromptText(null);
 182     }
 183 
 184     @Override
 185     public Node getValueEditor() {
 186         Node valueEditor;
 187         if (i18nMode) {
 188             valueEditor = i18nHBox;
 189         } else {
 190             valueEditor = textNode;
 191         }
 192 
 193         return super.handleGenericModes(valueEditor);
 194     }
 195 
 196     @Override
 197     protected void valueIsIndeterminate() {
 198         handleIndeterminate(textNode);
 199     }
 200 
 201     protected void switchToTextArea() {
 202         if (textNode instanceof TextArea) {
 203             return;
 204         }
 205         // Move the node from TextField to TextArea
 206         TextArea textArea = new TextArea(textNode.getText());
 207         setTextEditorBehavior(this, textArea, valueListener);
 208         textArea.setPrefRowCount(5);
 209         setLayoutFormat(LayoutFormat.SIMPLE_LINE_TOP);
 210         if (textNode.getParent() != null) {
 211             // textNode is already in scene graph
 212             EditorUtils.replaceNode(textNode, textArea, getLayoutFormat());
 213         }
 214         textNode = textArea;
 215     }
 216 
 217     protected void switchToTextField() {
 218         if (textNode instanceof TextField) {
 219             return;
 220         }
 221         // Move the node from TextArea to TextField.
 222         // The current text is compacted to a single line.
 223         String val = textNode.getText().replace("\n", "");//NOI18N
 224         TextField textField = new TextField(val);
 225         setTextEditorBehavior(this, textField, valueListener);
 226         setLayoutFormat(LayoutFormat.SIMPLE_LINE_CENTERED);
 227         if (textNode.getParent() != null) {
 228             // textNode is already in scene graph
 229             EditorUtils.replaceNode(textNode, textField, getLayoutFormat());
 230         }
 231         textNode = textField;
 232     }
 233 
 234     private void wrapInHBox() {
 235         i18nHBox = new HBox();
 236         i18nHBox.setAlignment(Pos.CENTER);
 237         EditorUtils.replaceNode(textNode, i18nHBox, null);
 238         Label percentLabel = new Label(PERCENT_STR);
 239         percentLabel.getStyleClass().add("symbol-prefix"); //NOI18N
 240         i18nHBox.getChildren().addAll(percentLabel, textNode);
 241         HBox.setHgrow(percentLabel, Priority.NEVER);
 242         // we have to set a small pref width for the text node else it will
 243         // revert to it's API set pref width which is too wide
 244         textNode.setPrefWidth(30.0);
 245         HBox.setHgrow(textNode, Priority.ALWAYS);
 246     }
 247 
 248     private void unwrapHBox() {
 249         i18nHBox.getChildren().remove(textNode);
 250         EditorUtils.replaceNode(i18nHBox, textNode, null);
 251     }
 252 
 253     private static boolean containsLineFeed(String str) {
 254         return str.contains("\n"); //NOI18N
 255     }
 256 
 257     @Override
 258     public void requestFocus() {
 259         EditorUtils.doNextFrame(() -> textNode.requestFocus());
 260     }
 261 
 262     private void updateMenuItems() {
 263         if (i18nMode) {
 264             i18nMenuItem.setText(I18N_OFF);
 265             multilineMenuItem.setDisable(true);
 266         } else {
 267             i18nMenuItem.setText(I18N_ON);
 268             multilineMenuItem.setDisable(false);
 269         }
 270 
 271         if (multiLineMode) {
 272             multilineMenuItem.setText(SINGLE_LINE);
 273             i18nMenuItem.setDisable(true);
 274         } else {
 275             multilineMenuItem.setText(MULTI_LINE);
 276             i18nMenuItem.setDisable(false);
 277         }
 278 
 279         if (!multiLineSupported) {
 280             multilineMenuItem.setDisable(true);
 281         }
 282     }
 283 }