private void updateOpenRevealMenuItems() {
   // Get the file name part of the suffix
   String suffix = new PrefixedValue(getValue()).getSuffix();
   String fileName = null;
   if (!suffix.isEmpty()) {
     String[] urlParts = suffix.split("\\/");
     fileName = urlParts[urlParts.length - 1];
     // On windows, we may have "\" separators.
     urlParts = fileName.split("\\\\");
     fileName = urlParts[urlParts.length - 1];
   }
   if (fileName != null) {
     openMi.setVisible(true);
     revealMi.setVisible(true);
     openMi.setText(I18N.getString("inspector.list.open", fileName));
     if (EditorPlatform.IS_MAC) {
       revealMi.setText(I18N.getString("inspector.list.reveal.finder", fileName));
     } else {
       revealMi.setText(I18N.getString("inspector.list.reveal.explorer", fileName));
     }
   } else {
     openMi.setVisible(false);
     revealMi.setVisible(false);
   }
 }
    // Method to please FindBugs
    private void initialize(String url) {
      setValue(url);
      EventHandler<ActionEvent> onActionListener =
          new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
              //                    System.out.println("StylesheetItem : onActionListener");
              if (getValue().equals(currentValue)) {
                // no change
                return;
              }
              if (stylesheetTf.getText().isEmpty()) {
                remove(null);
              }
              //                        System.out.println("StyleEditorItem : COMMIT");
              editor.commit(StylesheetItem.this);
              if (event != null && event.getSource() instanceof TextField) {
                ((TextField) event.getSource()).selectAll();
              }
              updateButtons();
              updateOpenRevealMenuItems();
              currentValue = getValue();
            }
          };

      ChangeListener<String> textPropertyChange =
          new ChangeListener<String>() {

            @Override
            public void changed(
                ObservableValue<? extends String> ov, String prevText, String newText) {
              if (prevText.isEmpty() || newText.isEmpty()) {
                // Text changed FROM empty value, or TO empty value: buttons status change
                updateButtons();
                updateOpenRevealMenuItems();
              }
            }
          };
      stylesheetTf.textProperty().addListener(textPropertyChange);
      updateButtons();

      setTextEditorBehavior(stylesheetTf, onActionListener);

      // Initialize menu items text
      removeMi.setText(I18N.getString("inspector.list.remove"));
      moveUpMi.setText(I18N.getString("inspector.list.moveup"));
      moveDownMi.setText(I18N.getString("inspector.list.movedown"));
    }
  @FXML
  void chooseStylesheet(ActionEvent event) {

    String[] extensions = {"*.css"}; // NOI18N
    FileChooser fileChooser = new FileChooser();
    fileChooser.setTitle(I18N.getString("inspector.select.css.title"));
    fileChooser
        .getExtensionFilters()
        .add(
            new FileChooser.ExtensionFilter(
                I18N.getString("inspector.select.css.filter"), Arrays.asList(extensions)));
    File file = fileChooser.showOpenDialog(root.getScene().getWindow());
    if ((file == null)) {
      return;
    }
    URL url;
    try {
      url = file.toURI().toURL();
    } catch (MalformedURLException ex) {
      throw new RuntimeException("Invalid URL", ex); // NOI18N
    }
    if (alreadyUsed(url.toExternalForm())) {
      System.err.println(
          I18N.getString("inspector.stylesheet.alreadyexist", url)); // should go to message panel
      return;
    }

    switchToItemList();
    // Add editor item
    String urlStr;
    if (fxmlFileLocation != null) {
      // If the document exists, make the type as document relative by default.
      urlStr = PrefixedValue.makePrefixedValue(url, fxmlFileLocation).toString();
      switchType(Type.DOCUMENT_RELATIVE_PATH);
    } else {
      urlStr = url.toExternalForm();
      switchType(Type.PLAIN_STRING);
    }
    addItem(new StylesheetItem(this, urlStr));

    // Workaround for RT-34863: Reload of an updated css file has no effect.
    // This reset the whole CSS from top. Would need to be moved on the FXOM side.
    Deprecation.reapplyCSS(root.getScene());

    userUpdateValueProperty(getValue());
  }
  private void updateContentGroup() {

    /*
     * fxomRoot
     */

    final String statusMessageText, statusStyleClass;
    contentGroup.getChildren().clear();

    if (fxomDocument == null) {
      statusMessageText = "FXOMDocument is null"; // NOI18N
      statusStyleClass = "stage-prompt"; // NOI18N
    } else if (fxomDocument.getFxomRoot() == null) {
      statusMessageText = I18N.getString("content.label.status.invitation");
      statusStyleClass = "stage-prompt"; // NOI18N
    } else {
      final Object userSceneGraph = fxomDocument.getSceneGraphRoot();
      if (userSceneGraph instanceof Node) {
        final Node rootNode = (Node) userSceneGraph;
        assert rootNode.getParent() == null;
        contentGroup.getChildren().add(rootNode);
        layoutContent(true /* applyCSS */);
        if (layoutException == null) {
          statusMessageText = ""; // NOI18N
          statusStyleClass = "stage-prompt-default"; // NOI18N
        } else {
          contentGroup.getChildren().clear();
          statusMessageText = I18N.getString("content.label.status.cannot.display");
          statusStyleClass = "stage-prompt"; // NOI18N
        }
      } else {
        statusMessageText = I18N.getString("content.label.status.cannot.display");
        statusStyleClass = "stage-prompt"; // NOI18N
      }
    }

    backgroundPane.setText(statusMessageText);
    backgroundPane.getStyleClass().clear();
    backgroundPane.getStyleClass().add(statusStyleClass);

    // If layoutException != null, then this layout call is required
    // so that backgroundPane updates its message... Strange...
    backgroundPane.layout();

    adjustWorkspace();
  }
 private void open(EditorItem source) {
   String urlStr = getUrl(source);
   if (urlStr == null) {
     return;
   }
   try {
     EditorPlatform.open(urlStr);
   } catch (IOException ex) {
     System.err.println(
         I18N.getString(
             "inspector.stylesheet.cannotopen",
             urlStr + " : " + ex)); // should go to message panel
   }
 }
 private void reveal(EditorItem source) {
   String urlStr = getUrl(source);
   if (urlStr == null) {
     return;
   }
   try {
     File file = URLUtils.getFile(urlStr);
     if (file == null) { // urlStr is not a file URL
       return;
     }
     EditorPlatform.revealInFileBrowser(file);
   } catch (URISyntaxException | IOException ex) {
     System.err.println(
         I18N.getString(
             "inspector.stylesheet.cannotreveal",
             urlStr + " : " + ex)); // should go to message panel
   }
 }
  private void initialize(boolean multiLineSupported) {
    this.multiLineSupported = multiLineSupported;
    valueListener =
        event -> {
          userUpdateValueProperty(getValue());
          textNode.selectAll();
        };
    setTextEditorBehavior(this, textNode, valueListener);

    getMenu().getItems().add(i18nMenuItem);
    getMenu().getItems().add(multilineMenuItem);

    i18nMenuItem.setOnAction(
        e -> {
          if (!i18nMode) {
            setValue(
                new PrefixedValue(
                        PrefixedValue.Type.RESOURCE_KEY, I18N.getString("inspector.i18n.dummykey"))
                    .toString());
          } else {
            setValue(""); // NOI18N
          }
          I18nStringEditor.this.getCommitListener().handle(null);
          updateMenuItems();
        });
    multilineMenuItem.setOnAction(
        e -> {
          if (!multiLineMode) {
            switchToTextArea();
          } else {
            switchToTextField();
          }
          multiLineMode = !multiLineMode;
          updateMenuItems();
        });
  }
 @Override
 protected String makeDescription() {
   return I18N.getString("label.action.edit.add.context.menu");
 }
/**
 * Editor of the 'stylesheet' property. It may contain several stylesheets (css) files, that are
 * handled by an inline class (StylesheetItem).
 */
public class StylesheetEditor extends InlineListEditor {

  private final StackPane root = new StackPane();
  private final Parent rootInitialBt;

  private final MenuItem documentRelativeMenuItem =
      new MenuItem(I18N.getString("inspector.resource.documentrelative"));
  private final MenuItem classPathRelativeMenuItem =
      new MenuItem(I18N.getString("inspector.resource.classpathrelative"));
  private final MenuItem absoluteMenuItem =
      new MenuItem(I18N.getString("inspector.resource.absolute"));

  private Type type;
  private URL fxmlFileLocation;

  @SuppressWarnings("LeakingThisInConstructor")
  public StylesheetEditor(
      IMetadata metadata,
      ValuePropertyMetadata propMeta,
      Set<Class<?>> selectedClasses,
      URL fxmlFileLocation) {
    super(metadata, propMeta, selectedClasses);
    this.fxmlFileLocation = fxmlFileLocation;
    setLayoutFormat(PropertyEditor.LayoutFormat.DOUBLE_LINE);
    // Add initial button
    rootInitialBt = EditorUtils.loadFxml("StylesheetEditorInitialBt.fxml", this); // NOI18N
    root.getChildren().add(rootInitialBt);
    // Set the initial value to empty list (instead of null)
    valueProperty().setValue(FXCollections.observableArrayList());

    documentRelativeMenuItem.setOnAction(
        new EventHandler<ActionEvent>() {
          @Override
          public void handle(ActionEvent e) {
            switchType(Type.DOCUMENT_RELATIVE_PATH);
          }
        });
    classPathRelativeMenuItem.setOnAction(
        new EventHandler<ActionEvent>() {
          @Override
          public void handle(ActionEvent e) {
            switchType(Type.CLASSLOADER_RELATIVE_PATH);
          }
        });
    absoluteMenuItem.setOnAction(
        new EventHandler<ActionEvent>() {
          @Override
          public void handle(ActionEvent e) {
            switchType(Type.PLAIN_STRING);
          }
        });
    getMenu()
        .getItems()
        .addAll(documentRelativeMenuItem, classPathRelativeMenuItem, absoluteMenuItem);
  }

  @Override
  public Node getValueEditor() {
    return super.handleGenericModes(root);
  }

  @Override
  public Object getValue() {
    List<String> value = FXCollections.observableArrayList();
    // Group all the item values in a list
    for (EditorItem stylesheetItem : getEditorItems()) {
      String itemValue = stylesheetItem.getValue();
      if (itemValue.isEmpty()) {
        continue;
      }
      value.add(itemValue);
    }
    if (value.isEmpty()) {
      // no stylesheet
      return super.getPropertyMeta().getDefaultValueObject();
    } else {
      type = getType(value);
      return value;
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  public void setValue(Object value) {
    if (value == null) {
      reset();
      return;
    }
    assert value instanceof List;
    if (((List) value).isEmpty()) {
      reset();
      return;
    }
    // Warning : value is the editing list.
    // We do not want to set the valueProperty() to editing list
    setValueGeneric(value);
    if (isSetValueDone()) {
      return;
    }

    type = getType((List<String>) value);
    updateMenuItems();
    Iterator<EditorItem> itemsIter = new ArrayList<>(getEditorItems()).iterator();
    for (String item : (List<String>) value) {
      item = item.trim();
      if (item.isEmpty()) {
        continue;
      }
      EditorItem editorItem;
      if (itemsIter.hasNext()) {
        // re-use the current items first
        editorItem = itemsIter.next();
      } else {
        // additional items required
        editorItem = addItem(new StylesheetItem(this, item));
      }
      editorItem.setValue(item);
    }
    // Empty the remaining items, if needed
    while (itemsIter.hasNext()) {
      EditorItem editorItem = itemsIter.next();
      removeItem(editorItem);
    }
    switchToItemList();
  }

  public void reset(
      ValuePropertyMetadata propMeta, Set<Class<?>> selectedClasses, URL fxmlFileLocation) {
    super.reset(propMeta, selectedClasses, true);
    this.fxmlFileLocation = fxmlFileLocation;
    switchToInitialButton();
  }

  @Override
  protected void reset() {
    super.reset(true);
    switchToInitialButton();
  }

  @Override
  public void requestFocus() {
    EditorItem firstItem = getEditorItems().get(0);
    assert firstItem instanceof StylesheetItem;
    ((StylesheetItem) firstItem).requestFocus();
  }

  @Override
  public void remove(EditorItem source) {
    super.remove(source, true);
    if (super.getEditorItems().isEmpty()) {
      // Switch to initial button
      switchToInitialButton();
    }
  }

  private void open(EditorItem source) {
    String urlStr = getUrl(source);
    if (urlStr == null) {
      return;
    }
    try {
      EditorPlatform.open(urlStr);
    } catch (IOException ex) {
      System.err.println(
          I18N.getString(
              "inspector.stylesheet.cannotopen",
              urlStr + " : " + ex)); // should go to message panel
    }
  }

  private void reveal(EditorItem source) {
    String urlStr = getUrl(source);
    if (urlStr == null) {
      return;
    }
    try {
      File file = URLUtils.getFile(urlStr);
      if (file == null) { // urlStr is not a file URL
        return;
      }
      EditorPlatform.revealInFileBrowser(file);
    } catch (URISyntaxException | IOException ex) {
      System.err.println(
          I18N.getString(
              "inspector.stylesheet.cannotreveal",
              urlStr + " : " + ex)); // should go to message panel
    }
  }

  private String getUrl(EditorItem source) {
    URL url = EditorUtils.getUrl(source.getValue(), fxmlFileLocation);
    if (url == null) {
      return null;
    }
    String urlStr = url.toExternalForm();
    return urlStr;
  }

  @FXML
  void chooseStylesheet(ActionEvent event) {

    String[] extensions = {"*.css"}; // NOI18N
    FileChooser fileChooser = new FileChooser();
    fileChooser.setTitle(I18N.getString("inspector.select.css.title"));
    fileChooser
        .getExtensionFilters()
        .add(
            new FileChooser.ExtensionFilter(
                I18N.getString("inspector.select.css.filter"), Arrays.asList(extensions)));
    File file = fileChooser.showOpenDialog(root.getScene().getWindow());
    if ((file == null)) {
      return;
    }
    URL url;
    try {
      url = file.toURI().toURL();
    } catch (MalformedURLException ex) {
      throw new RuntimeException("Invalid URL", ex); // NOI18N
    }
    if (alreadyUsed(url.toExternalForm())) {
      System.err.println(
          I18N.getString("inspector.stylesheet.alreadyexist", url)); // should go to message panel
      return;
    }

    switchToItemList();
    // Add editor item
    String urlStr;
    if (fxmlFileLocation != null) {
      // If the document exists, make the type as document relative by default.
      urlStr = PrefixedValue.makePrefixedValue(url, fxmlFileLocation).toString();
      switchType(Type.DOCUMENT_RELATIVE_PATH);
    } else {
      urlStr = url.toExternalForm();
      switchType(Type.PLAIN_STRING);
    }
    addItem(new StylesheetItem(this, urlStr));

    // Workaround for RT-34863: Reload of an updated css file has no effect.
    // This reset the whole CSS from top. Would need to be moved on the FXOM side.
    Deprecation.reapplyCSS(root.getScene());

    userUpdateValueProperty(getValue());
  }

  @FXML
  void buttonTyped(KeyEvent event) {
    if (event.getCode() == KeyCode.ENTER) {
      chooseStylesheet(null);
    }
  }

  private void switchToItemList() {
    // Replace initial button by the item list (vbox)
    if (root.getChildren().contains(rootInitialBt)) {
      root.getChildren().remove(rootInitialBt);
      root.getChildren().add(super.getValueEditor());
    }
  }

  private void switchToInitialButton() {
    // Replace the item list (vbox) by initial button
    root.getChildren().clear();
    root.getChildren().add(rootInitialBt);
  }

  private boolean alreadyUsed(String url) {
    for (EditorItem item : super.getEditorItems()) {
      if (item.getValue().equals(url)) {
        return true;
      }
    }
    return false;
  }

  private void switchType(Type type) {
    this.type = type;
    updateMenuItems();
    for (EditorItem editorItem : getEditorItems()) {
      assert editorItem instanceof StylesheetItem;
      StylesheetItem stylesheetItem = (StylesheetItem) editorItem;
      URL url = EditorUtils.getUrl(stylesheetItem.getValue(), fxmlFileLocation);
      String value = null;
      if ((url == null) || (type == Type.CLASSLOADER_RELATIVE_PATH)) {
        // In this case we empty the text field (i.e. suffix) content
        value = new PrefixedValue(type, "").toString(); // NOI18N
      } else if (type == Type.PLAIN_STRING) {
        value = url.toExternalForm();
      } else if (type == Type.DOCUMENT_RELATIVE_PATH) {
        value = PrefixedValue.makePrefixedValue(url, fxmlFileLocation).toString();
      }
      stylesheetItem.setValue(value);
      commit(stylesheetItem);
    }
  }

  private Type getType(List<String> styleSheets) {
    Type commonType = null;
    for (String styleSheet : styleSheets) {
      if (commonType == null) {
        commonType = getType(styleSheet);
      } else {
        if (commonType != getType(styleSheet)) {
          // mix of different types: set all to document relative
          commonType = Type.DOCUMENT_RELATIVE_PATH;
          break;
        }
      }
    }
    return commonType;
  }

  private static Type getType(String styleSheet) {
    return (new PrefixedValue(styleSheet)).getType();
  }

  private void updateMenuItems() {
    documentRelativeMenuItem.setDisable(false);
    classPathRelativeMenuItem.setDisable(false);
    absoluteMenuItem.setDisable(false);
    if (fxmlFileLocation == null) {
      documentRelativeMenuItem.setDisable(true);
    }
    if (type == Type.DOCUMENT_RELATIVE_PATH) {
      documentRelativeMenuItem.setDisable(true);
    } else if (type == Type.CLASSLOADER_RELATIVE_PATH) {
      classPathRelativeMenuItem.setDisable(true);
    } else if (type == Type.PLAIN_STRING) {
      absoluteMenuItem.setDisable(true);
    }
  }

  /**
   * **************************************************************************
   *
   * <p>StyleClass item : styleClass text fields, and +/action buttons.
   *
   * <p>**************************************************************************
   */
  private class StylesheetItem implements EditorItem {

    @FXML private Button plusBt;
    @FXML private MenuItem removeMi;
    @FXML private MenuItem moveUpMi;
    @FXML private MenuItem moveDownMi;
    @FXML private MenuItem openMi;
    @FXML private MenuItem revealMi;
    @FXML private Label prefixLb;
    @FXML private TextField stylesheetTf;

    private final Pane root;
    private String currentValue;
    private final EditorItemDelegate editor;
    private Type itemType = Type.PLAIN_STRING;

    @SuppressWarnings("LeakingThisInConstructor")
    public StylesheetItem(EditorItemDelegate editor, String url) {
      //            System.out.println("New StylesheetItem.");
      this.editor = editor;
      Parent parentRoot = EditorUtils.loadFxml("StylesheetEditorItem.fxml", this);
      assert parentRoot instanceof Pane;
      root = (Pane) parentRoot;

      initialize(url);
    }

    // Method to please FindBugs
    private void initialize(String url) {
      setValue(url);
      EventHandler<ActionEvent> onActionListener =
          new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
              //                    System.out.println("StylesheetItem : onActionListener");
              if (getValue().equals(currentValue)) {
                // no change
                return;
              }
              if (stylesheetTf.getText().isEmpty()) {
                remove(null);
              }
              //                        System.out.println("StyleEditorItem : COMMIT");
              editor.commit(StylesheetItem.this);
              if (event != null && event.getSource() instanceof TextField) {
                ((TextField) event.getSource()).selectAll();
              }
              updateButtons();
              updateOpenRevealMenuItems();
              currentValue = getValue();
            }
          };

      ChangeListener<String> textPropertyChange =
          new ChangeListener<String>() {

            @Override
            public void changed(
                ObservableValue<? extends String> ov, String prevText, String newText) {
              if (prevText.isEmpty() || newText.isEmpty()) {
                // Text changed FROM empty value, or TO empty value: buttons status change
                updateButtons();
                updateOpenRevealMenuItems();
              }
            }
          };
      stylesheetTf.textProperty().addListener(textPropertyChange);
      updateButtons();

      setTextEditorBehavior(stylesheetTf, onActionListener);

      // Initialize menu items text
      removeMi.setText(I18N.getString("inspector.list.remove"));
      moveUpMi.setText(I18N.getString("inspector.list.moveup"));
      moveDownMi.setText(I18N.getString("inspector.list.movedown"));
    }

    @Override
    public final Node getNode() {
      return root;
    }

    @Override
    public String getValue() {
      String suffix;
      if (stylesheetTf.getText().isEmpty()) {
        return ""; // NOI18N
      } else {
        suffix = stylesheetTf.getText().trim();
      }
      return (new PrefixedValue(itemType, suffix)).toString();
    }

    @Override
    public void setValue(String styleSheet) {
      PrefixedValue prefixedValue = new PrefixedValue(styleSheet);
      itemType = prefixedValue.getType();
      handlePrefix(itemType);
      if (prefixedValue.getSuffix() != null) {
        stylesheetTf.setText(prefixedValue.getSuffix().trim());
      } else {
        // may happen if wrong style sheet
        stylesheetTf.setText(""); // NOI18N
      }
      updateButtons();
      updateOpenRevealMenuItems();
      currentValue = getValue();
    }

    @Override
    public void reset() {
      stylesheetTf.setText(""); // NOI18N
      stylesheetTf.setPromptText(null);
    }

    @Override
    public void setValueAsIndeterminate() {
      handleIndeterminate(stylesheetTf);
    }

    protected void requestFocus() {
      EditorUtils.doNextFrame(
          new Runnable() {

            @Override
            public void run() {
              stylesheetTf.requestFocus();
            }
          });
    }

    @Override
    public MenuItem getMoveUpMenuItem() {
      return moveUpMi;
    }

    @Override
    public MenuItem getMoveDownMenuItem() {
      return moveDownMi;
    }

    @Override
    public MenuItem getRemoveMenuItem() {
      return removeMi;
    }

    @Override
    public Button getPlusButton() {
      return plusBt;
    }

    @FXML
    void chooseStylesheet(ActionEvent event) {
      ((StylesheetEditor) editor).chooseStylesheet(event);
    }

    @FXML
    void remove(ActionEvent event) {
      editor.remove(this);
    }

    @FXML
    void up(ActionEvent event) {
      editor.up(this);
    }

    @FXML
    void down(ActionEvent event) {
      editor.down(this);
    }

    @FXML
    void open(ActionEvent event) {
      ((StylesheetEditor) editor).open(this);
    }

    @FXML
    void reveal(ActionEvent event) {
      ((StylesheetEditor) editor).reveal(this);
    }

    @FXML
    void plusBtTyped(KeyEvent event) {
      if (event.getCode() == KeyCode.ENTER) {
        chooseStylesheet(null);
      }
    }

    private void updateOpenRevealMenuItems() {
      // Get the file name part of the suffix
      String suffix = new PrefixedValue(getValue()).getSuffix();
      String fileName = null;
      if (!suffix.isEmpty()) {
        String[] urlParts = suffix.split("\\/");
        fileName = urlParts[urlParts.length - 1];
        // On windows, we may have "\" separators.
        urlParts = fileName.split("\\\\");
        fileName = urlParts[urlParts.length - 1];
      }
      if (fileName != null) {
        openMi.setVisible(true);
        revealMi.setVisible(true);
        openMi.setText(I18N.getString("inspector.list.open", fileName));
        if (EditorPlatform.IS_MAC) {
          revealMi.setText(I18N.getString("inspector.list.reveal.finder", fileName));
        } else {
          revealMi.setText(I18N.getString("inspector.list.reveal.explorer", fileName));
        }
      } else {
        openMi.setVisible(false);
        revealMi.setVisible(false);
      }
    }

    private void updateButtons() {
      if (stylesheetTf.getText().isEmpty()) {
        // if no content, disable plus
        plusBt.setDisable(true);
        removeMi.setDisable(false);
      } else {
        // enable plus and minus
        plusBt.setDisable(false);
        removeMi.setDisable(false);
      }
    }

    protected void disablePlusButton(boolean disable) {
      plusBt.setDisable(disable);
    }

    protected void disableRemove(boolean disable) {
      removeMi.setDisable(disable);
    }

    protected void handlePrefix(Type type) {
      this.itemType = type;
      if (type == Type.DOCUMENT_RELATIVE_PATH) {
        setPrefix(FXMLLoader.RELATIVE_PATH_PREFIX);
      } else if (type == Type.CLASSLOADER_RELATIVE_PATH) {
        setPrefix(FXMLLoader.RELATIVE_PATH_PREFIX + "/"); // NOI18N
      } else {
        // absolute
        removeLabel();
      }
    }

    private void setPrefix(String str) {
      if (!prefixLb.isVisible()) {
        prefixLb.setVisible(true);
        prefixLb.setManaged(true);
      }
      prefixLb.setText(str);
    }

    private void removeLabel() {
      prefixLb.setVisible(false);
      prefixLb.setManaged(false);
    }
  }
}
/** String editor with I18n + multi-line handling. */
public class I18nStringEditor extends PropertyEditor {

  private static final String PERCENT_STR = "%"; // NOI18N
  private TextInputControl textNode = new TextField();
  private HBox i18nHBox = null;
  private EventHandler<ActionEvent> valueListener;
  private final MenuItem i18nMenuItem = new MenuItem();
  private final String I18N_ON = I18N.getString("inspector.i18n.on");
  private final String I18N_OFF = I18N.getString("inspector.i18n.off");
  private final MenuItem multilineMenuItem = new MenuItem();
  private final String MULTI_LINE = I18N.getString("inspector.i18n.multiline");
  private final String SINGLE_LINE = I18N.getString("inspector.i18n.singleline");
  private boolean multiLineSupported = false;
  // Specific states
  private boolean i18nMode = false;
  private boolean multiLineMode = false;

  public I18nStringEditor(
      ValuePropertyMetadata propMeta, Set<Class<?>> selectedClasses, boolean multiLineSupported) {
    super(propMeta, selectedClasses);
    initialize(multiLineSupported);
  }

  private void initialize(boolean multiLineSupported) {
    this.multiLineSupported = multiLineSupported;
    valueListener =
        event -> {
          userUpdateValueProperty(getValue());
          textNode.selectAll();
        };
    setTextEditorBehavior(this, textNode, valueListener);

    getMenu().getItems().add(i18nMenuItem);
    getMenu().getItems().add(multilineMenuItem);

    i18nMenuItem.setOnAction(
        e -> {
          if (!i18nMode) {
            setValue(
                new PrefixedValue(
                        PrefixedValue.Type.RESOURCE_KEY, I18N.getString("inspector.i18n.dummykey"))
                    .toString());
          } else {
            setValue(""); // NOI18N
          }
          I18nStringEditor.this.getCommitListener().handle(null);
          updateMenuItems();
        });
    multilineMenuItem.setOnAction(
        e -> {
          if (!multiLineMode) {
            switchToTextArea();
          } else {
            switchToTextField();
          }
          multiLineMode = !multiLineMode;
          updateMenuItems();
        });
  }

  @Override
  public Object getValue() {
    String val = textNode.getText();
    if (i18nMode) {
      val = new PrefixedValue(PrefixedValue.Type.RESOURCE_KEY, val).toString();
    } else {
      val = EditorUtils.getPlainString(val);
    }
    return val;
  }

  @Override
  public void setValue(Object value) {
    setValueGeneric(value);
    if (isSetValueDone()) {
      return;
    }

    if (value == null) {
      textNode.setText(null);
      return;
    }
    assert value instanceof String;
    String val = (String) value;
    PrefixedValue prefixedValue = new PrefixedValue(val);
    String suffix = prefixedValue.getSuffix();

    // Handle i18n
    if (prefixedValue.isResourceKey()) {
      if (!i18nMode) {
        wrapInHBox();
        i18nMode = true;
      }
    } else if (i18nMode) {
      // no percent + i18nMode
      unwrapHBox();
      i18nMode = false;
    }

    // Handle multi-line
    if (containsLineFeed(prefixedValue.toString())) {
      if (i18nMode) {
        // multi-line + i18n ==> set as i18n only
        multiLineMode = false;
        switchToTextField();
      } else {
        if (!multiLineMode) {
          multiLineMode = true;
          switchToTextArea();
        }
      }
    } else {
      // no line feed
      if (multiLineMode) {
        multiLineMode = false;
        switchToTextField();
      }
    }

    if (i18nMode) {
      textNode.setText(suffix);
    } else {
      // We may have other special characters (@, $, ...) to display in the text field
      textNode.setText(prefixedValue.toString());
    }
    updateMenuItems();
  }

  public void reset(
      ValuePropertyMetadata propMeta, Set<Class<?>> selectedClasses, boolean multiLineSupported) {
    super.reset(propMeta, selectedClasses);
    this.multiLineSupported = multiLineSupported;
    textNode.setPromptText(null);
  }

  @Override
  public Node getValueEditor() {
    Node valueEditor;
    if (i18nMode) {
      valueEditor = i18nHBox;
    } else {
      valueEditor = textNode;
    }

    return super.handleGenericModes(valueEditor);
  }

  @Override
  protected void valueIsIndeterminate() {
    handleIndeterminate(textNode);
  }

  protected void switchToTextArea() {
    if (textNode instanceof TextArea) {
      return;
    }
    // Move the node from TextField to TextArea
    TextArea textArea = new TextArea(textNode.getText());
    setTextEditorBehavior(this, textArea, valueListener);
    textArea.setPrefRowCount(5);
    setLayoutFormat(LayoutFormat.SIMPLE_LINE_TOP);
    if (textNode.getParent() != null) {
      // textNode is already in scene graph
      EditorUtils.replaceNode(textNode, textArea, getLayoutFormat());
    }
    textNode = textArea;
  }

  protected void switchToTextField() {
    if (textNode instanceof TextField) {
      return;
    }
    // Move the node from TextArea to TextField.
    // The current text is compacted to a single line.
    String val = textNode.getText().replace("\n", ""); // NOI18N
    TextField textField = new TextField(val);
    setTextEditorBehavior(this, textField, valueListener);
    setLayoutFormat(LayoutFormat.SIMPLE_LINE_CENTERED);
    if (textNode.getParent() != null) {
      // textNode is already in scene graph
      EditorUtils.replaceNode(textNode, textField, getLayoutFormat());
    }
    textNode = textField;
  }

  private void wrapInHBox() {
    i18nHBox = new HBox();
    i18nHBox.setAlignment(Pos.CENTER);
    EditorUtils.replaceNode(textNode, i18nHBox, null);
    Label percentLabel = new Label(PERCENT_STR);
    percentLabel.getStyleClass().add("symbol-prefix"); // NOI18N
    i18nHBox.getChildren().addAll(percentLabel, textNode);
    HBox.setHgrow(percentLabel, Priority.NEVER);
    // we have to set a small pref width for the text node else it will
    // revert to it's API set pref width which is too wide
    textNode.setPrefWidth(30.0);
    HBox.setHgrow(textNode, Priority.ALWAYS);
  }

  private void unwrapHBox() {
    i18nHBox.getChildren().remove(textNode);
    EditorUtils.replaceNode(i18nHBox, textNode, null);
  }

  private static boolean containsLineFeed(String str) {
    return str.contains("\n"); // NOI18N
  }

  @Override
  public void requestFocus() {
    EditorUtils.doNextFrame(() -> textNode.requestFocus());
  }

  private void updateMenuItems() {
    if (i18nMode) {
      i18nMenuItem.setText(I18N_OFF);
      multilineMenuItem.setDisable(true);
    } else {
      i18nMenuItem.setText(I18N_ON);
      multilineMenuItem.setDisable(false);
    }

    if (multiLineMode) {
      multilineMenuItem.setText(SINGLE_LINE);
      i18nMenuItem.setDisable(true);
    } else {
      multilineMenuItem.setText(MULTI_LINE);
      i18nMenuItem.setDisable(false);
    }

    if (!multiLineSupported) {
      multilineMenuItem.setDisable(true);
    }
  }
}