Class DefaultCodeEditorView

All Implemented Interfaces:
Styleable, EventTarget
Direct Known Subclasses:
JsonFormatterView

public class DefaultCodeEditorView extends BaseBorderView
Default code editor view built on RSyntaxTextArea.

This is the JavaFX counterpart of the reference code editor: the same component the reference used (RSyntaxTextArea in an RTextScrollPane), embedded into JavaFX with a SwingNode. That is deliberate - the editor look of the reference (background, gutter with line numbers and fold indicator, the fine gutter border, the caret and the token colours) is defined by the RSyntaxTextArea theme cfg/edit.xml, which ships with this framework and originates from the reference theme (only the gutter background, the gutter divider and the scrollbars — DarkScrollBarUI — were adjusted for this framework).

The pane chrome (header with title and icon, body, footer with note label and buttons) is not built here: it comes from the FXML (codeEditorPane.fxml), exactly as for every other pane. An instance is created with DefaultCodeEditorView(BaseApp, Stage) or DefaultCodeEditorView(BaseApp, Stage, String) and is afterwards set up with UI.setup(BaseApp, String, ch.autumo.ui.fx.view.BaseBorderView, String), which loads the FXML onto this view and binds the controller.

Title, icon, footer text and the additional footer buttons are not passed in a constructor: they are set on the controller before the stage is shown, see DefaultInfoController.customize(String, String, String) and DefaultCodeEditorController.setButtons(java.util.List).

Syntax highlighting is RSyntaxTextArea's own tokenizer; the language is selected with setSyntaxEditingStyle(String) using the SyntaxConstants values (JSON, YAML, properties, plain text, ...). It is never guessed from the content - setSourceFile(Path) derives it from the file name's extension, see syntaxStyleForExtension(String); an application adds its own formats with registerSyntaxStyle(String, String).

Extensible by subclassing: createTextArea(), createScrollPane(RSyntaxTextArea) and applyEditorTheme() are protected hooks an application can override to change the editor's behaviour or look. Note that all of them are invoked from the constructor, so overrides must build their object from the hook parameters and base-class state only - subclass fields are not yet initialised at that point.

Two things an application that uses the editor has to know:

  • It embeds Swing, and the AWT event dispatch thread is not a daemon thread: it keeps the JVM alive after the JavaFX runtime is gone. The application ends the JVM itself and says so with BaseApp.isJvmExitOnStop().
  • On macOS the AWT thread can block for ever in a native call while a Swing component is removed, and it holds the AWT tree lock while it does - a JavaFX thread that touches Swing in that moment waits for ever. The editor is therefore built on the AWT thread, the text is mirrored on the JavaFX side, and the JavaFX thread never waits for the AWT thread.

An application that has to know about the changes of the user overrides textChanged(); a text the application sets itself does not trigger it.

  • Field Details

    • STYLE_CLASS

      public static final String STYLE_CLASS
      CSS class of the whole pane, set by the FXML and used to scope the pane styles.
      See Also:
  • Constructor Details

    • DefaultCodeEditorView

      public DefaultCodeEditorView(BaseApp app, Stage stage)
      Default code editor view.
      Parameters:
      app - the application
      stage - stage
    • DefaultCodeEditorView

      public DefaultCodeEditorView(BaseApp app, Stage stage, String styleResourcePath)
      Default code editor view.
      Parameters:
      app - the application
      stage - stage
      styleResourcePath - relative style ('style/style.css') file name or fully qualified resource path
  • Method Details

    • disableInputMethods

      public static void disableInputMethods(Component component)
      Switches the input methods off for a component and all its children.

      Every AWT component has the input methods enabled by default (see the event mask of java.awt.Component), and while a component is removed macOS asks for its input context: Component.removeNotify() calls getInputContext(), which ends in the native input method of the operating system. That call can block the AWT event dispatch thread forever - the application then freezes and the JVM does not exit. A component without input methods is removed without that call.

      The whole hierarchy has to be covered, not only the text area: the gutter and its fold indicator are removed as well, and they ask for their input context, too.

      Parameters:
      component - the component and its children
    • createTextArea

      protected org.fife.ui.rsyntaxtextarea.RSyntaxTextArea createTextArea()
      Create the editor widget: an RSyntaxTextArea with the settings the reference editor had before its theme was applied (tab size, margin, line wrap, colours). The default syntax style is plain text; subclasses/callers select a language with setSyntaxEditingStyle(String).
      Returns:
      the editor widget
    • createScrollPane

      protected org.fife.ui.rtextarea.RTextScrollPane createScrollPane(org.fife.ui.rsyntaxtextarea.RSyntaxTextArea area)
      Create the scroll pane around the editor widget: line-number gutter with fold indicator, the fine frame of the reference (Constants.BG_COLOR_FIELD, 1px) and dark scrollbars (DarkScrollBarUI - Swing's own look & feel would paint light ones, which clash with the dark editor theme).
      Parameters:
      area - the editor widget
      Returns:
      the scroll pane holding the editor
    • applyEditorTheme

      protected void applyEditorTheme()
      Apply the RSyntaxTextArea theme and the token colours of the reference, in the theme order: theme first, then the JSON panel's token colours, then the explicit overrides (selection, current line, font, anti-aliasing).

      The theme is applied after the scroll pane exists, because it also styles the gutter: line numbers, gutter background, gutter border (the fine line right of the numbers) and the fold indicator colours come from cfg/edit.xml - derived from the file the reference shipped.

    • getEditorPane

      public Pane getEditorPane()
      Get the editor widget wrapped in its JavaFX container; this is the node the controller embeds into the pane body (see DefaultCodeEditorController.init(URL, ResourceBundle, double)). The container holds a SwingNode with the RSyntaxTextArea scroll pane.
      Returns:
      the scrollable editor widget
    • getTextArea

      public org.fife.ui.rsyntaxtextarea.RSyntaxTextArea getTextArea()
      Get the underlying editor widget.
      Returns:
      the code editor
    • getSwingNode

      public SwingNode getSwingNode()
      Get the swing node holding the editor, e.g. for focus handling.
      Returns:
      the swing node
    • getText

      public String getText()
      Get the current editor text.
      Returns:
      the text
    • setText

      public void setText(String text)
      Set the editor text.

      This is a change of the application, not of the user: textChanged() is not called for it.

      Parameters:
      text - the text
    • textChanged

      protected void textChanged()
      Called when the user changed the text in the editor.

      The default does nothing. An application that keeps the edited text somewhere else, or that has to mark the document as modified, overrides it:

      @Override
      protected void textChanged() {
          Platform.runLater(() -> markModified());
      }
      

      The call comes from the AWT event dispatch thread - the thread the editor lives on. An application that touches the JavaFX scene has to hand the call over with Platform.runLater(Runnable).

    • setSourceFile

      public void setSourceFile(Path file) throws IOException
      Load a file into the editor: its content (read as UTF-8) becomes the editor text and the syntax highlighting language is selected from the file name's extension, see syntaxStyleForExtension(String). An unknown extension results in plain text (SyntaxConstants.SYNTAX_STYLE_NONE) - the editor never guesses a language from the content.

      This is a convenience for the common "open this file" case; to set text and language separately (e.g. generated content), use setText(String) and setSyntaxEditingStyle(String) directly.

      Parameters:
      file - file to show in the editor
      Throws:
      IOException - if the file cannot be read
    • syntaxStyleForExtension

      public String syntaxStyleForExtension(String fileName)
      RSyntaxTextArea syntax style constant for a file name, derived from its extension (case-insensitive). Config/data formats first - these are the files this framework's editors are used for - then common source formats; everything else is plain text.

      Extensions registered with registerSyntaxStyle(String, String) are checked first, so an application can add its own formats to this editor.

      Parameters:
      fileName - file name (only the extension is looked at), may be null
      Returns:
      the SyntaxConstants value, SyntaxConstants.SYNTAX_STYLE_NONE if the extension is unknown
    • registerSyntaxStyle

      public void registerSyntaxStyle(String extension, String syntaxStyle)
      Registers a file-name extension for this editor, e.g. registerSyntaxStyle("ifcx", SyntaxConstants.SYNTAX_STYLE_YAML). Registered extensions are checked before the built-in table and overwrite a previous registration of the same extension.
      Parameters:
      extension - file-name extension without dot, case-insensitive, e.g. ifcx
      syntaxStyle - the RSyntaxTextArea syntax style constant to use for it
    • syntaxStyleFor

      public static String syntaxStyleFor(String fileName)
      RSyntaxTextArea syntax style constant for a file-name extension (case-insensitive). Config/data formats first - these are the files this framework's editors are used for - then common source formats; everything else is plain text.
      Parameters:
      fileName - file name (only the extension is looked at), may be null
      Returns:
      the SyntaxConstants value, SyntaxConstants.SYNTAX_STYLE_NONE if the extension is unknown
    • setSyntaxEditingStyle

      public void setSyntaxEditingStyle(String syntaxStyle)
      Select the syntax highlighting language, one of the SyntaxConstants values (SYNTAX_STYLE_JSON, SYNTAX_STYLE_YAML, SYNTAX_STYLE_PROPERTIES, SYNTAX_STYLE_NONE, ...). RSyntaxTextArea highlights exactly one language at a time.
      Parameters:
      syntaxStyle - the RSyntaxTextArea syntax style constant
    • caretToStart

      public void caretToStart()
      Move the caret to the document start, as the formatter did after formatting.