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 }