Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git

Upgrade to Java 14, runs successfully

AuthorDave Jarvis <email>
Date2020-05-15 13:27:58 GMT-0700
Commit8e0a5820a76648e3b7bbdad04d6242915866f9cd
Parent6c5ea17
Delta564 lines added, 577 lines removed, 13-line decrease
src/main/java/com/scrivenvar/MainWindow.java
package com.scrivenvar;
-import static com.scrivenvar.Constants.*;
-import static com.scrivenvar.Messages.get;
-import com.scrivenvar.definition.*;
-import com.scrivenvar.dialogs.RScriptDialog;
-import com.scrivenvar.editors.EditorPane;
-import com.scrivenvar.editors.VariableNameInjector;
-import com.scrivenvar.editors.markdown.MarkdownEditorPane;
-import com.scrivenvar.predicates.files.FileTypePredicate;
-import com.scrivenvar.preview.HTMLPreviewPane;
-import com.scrivenvar.processors.Processor;
-import com.scrivenvar.processors.ProcessorFactory;
-import com.scrivenvar.service.Options;
-import com.scrivenvar.service.Snitch;
-import com.scrivenvar.service.events.Notifier;
-import com.scrivenvar.util.Action;
-import com.scrivenvar.util.ActionUtils;
-import static com.scrivenvar.util.StageState.*;
-import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Observable;
-import java.util.Observer;
-import java.util.Optional;
-import java.util.function.Function;
-import java.util.prefs.Preferences;
-import javafx.application.Platform;
-import javafx.beans.binding.Bindings;
-import javafx.beans.binding.BooleanBinding;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.SimpleBooleanProperty;
-import javafx.beans.value.ObservableBooleanValue;
-import javafx.beans.value.ObservableValue;
-import javafx.collections.ListChangeListener.Change;
-import javafx.collections.ObservableList;
-import static javafx.event.Event.fireEvent;
-import javafx.geometry.Pos;
-import javafx.scene.Node;
-import javafx.scene.Scene;
-import javafx.scene.control.Alert;
-import javafx.scene.control.Alert.AlertType;
-import javafx.scene.control.Menu;
-import javafx.scene.control.MenuBar;
-import javafx.scene.control.SplitPane;
-import javafx.scene.control.Tab;
-import javafx.scene.control.TextField;
-import javafx.scene.control.ToolBar;
-import javafx.scene.control.TreeView;
-import javafx.scene.image.Image;
-import javafx.scene.image.ImageView;
-import static javafx.scene.input.KeyCode.ESCAPE;
-import javafx.scene.input.KeyEvent;
-import static javafx.scene.input.KeyEvent.CHAR_UNDEFINED;
-import static javafx.scene.input.KeyEvent.KEY_PRESSED;
-import javafx.scene.layout.BorderPane;
-import javafx.scene.layout.VBox;
-import javafx.scene.text.Text;
-import javafx.stage.Window;
-import javafx.stage.WindowEvent;
-import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST;
-import org.controlsfx.control.StatusBar;
-import org.fxmisc.richtext.model.TwoDimensional.Position;
-
-/**
- * Main window containing a tab pane in the center for file editors.
- *
- * @author Karl Tauber and White Magic Software, Ltd.
- */
-public class MainWindow implements Observer {
-
- private final Options options = Services.load( Options.class );
- private final Snitch snitch = Services.load( Snitch.class );
- private final Notifier notifier = Services.load( Notifier.class );
-
- private Scene scene;
- private MenuBar menuBar;
- private StatusBar statusBar;
- private Text lineNumberText;
- private TextField findTextField;
-
- private DefinitionSource definitionSource;
- private DefinitionPane definitionPane;
- private FileEditorTabPane fileEditorPane;
- private HTMLPreviewPane previewPane;
-
- /**
- * Prevents re-instantiation of processing classes.
- */
- private Map<FileEditorTab, Processor<String>> processors;
-
- /**
- * Listens on the definition pane for double-click events.
- */
- private VariableNameInjector variableNameInjector;
-
- public MainWindow() {
- initLayout();
- initFindInput();
- initSnitch();
- initDefinitionListener();
- initTabAddedListener();
- initTabChangedListener();
- initPreferences();
- }
-
- /**
- * Watch for changes to external files. In particular, this awaits
- * modifications to any XSL files associated with XML files being edited. When
- * an XSL file is modified (external to the application), the snitch's ears
- * perk up and the file is reloaded. This keeps the XSL transformation up to
- * date with what's on the file system.
- */
- private void initSnitch() {
- getSnitch().addObserver( this );
- }
-
- /**
- * Initialize the find input text field to listen on F3, ENTER, and ESCAPE key
- * presses.
- */
- private void initFindInput() {
- final TextField input = getFindTextField();
-
- input.setOnKeyPressed( (KeyEvent event) -> {
- switch( event.getCode() ) {
- case F3:
- case ENTER:
- findNext();
- break;
- case F:
- if( !event.isControlDown() ) {
- break;
- }
- case ESCAPE:
- getStatusBar().setGraphic( null );
- getActiveFileEditor().getEditorPane().requestFocus();
- break;
- }
- } );
-
- // Remove when the input field loses focus.
- input.focusedProperty().addListener(
- (
- final ObservableValue<? extends Boolean> focused,
- final Boolean oFocus,
- final Boolean nFocus) -> {
- if( !nFocus ) {
- getStatusBar().setGraphic( null );
- }
- }
- );
- }
-
- /**
- * Listen for file editor tab pane to receive an open definition source event.
- */
- private void initDefinitionListener() {
- getFileEditorPane().onOpenDefinitionFileProperty().addListener(
- (ObservableValue<? extends Path> definitionFile,
- final Path oldPath, final Path newPath) -> {
- openDefinition( newPath );
-
- // Indirectly refresh the resolved map.
- setProcessors( null );
- updateDefinitionPane();
-
- try {
- getSnitch().ignore( oldPath );
- getSnitch().listen( newPath );
- }
- catch( final IOException ex ) {
- error( ex );
- }
-
- // Will create new processors and therefore a new resolved map.
- refreshSelectedTab( getActiveFileEditor() );
- }
- );
- }
-
- /**
- * When tabs are added, hook the various change listeners onto the new tab so
- * that the preview pane refreshes as necessary.
- */
- private void initTabAddedListener() {
- final FileEditorTabPane editorPane = getFileEditorPane();
-
- // Make sure the text processor kicks off when new files are opened.
- final ObservableList<Tab> tabs = editorPane.getTabs();
-
- // Update the preview pane on tab changes.
- tabs.addListener(
- (final Change<? extends Tab> change) -> {
- while( change.next() ) {
- if( change.wasAdded() ) {
- // Multiple tabs can be added simultaneously.
- for( final Tab newTab : change.getAddedSubList() ) {
- final FileEditorTab tab = (FileEditorTab)newTab;
-
- initTextChangeListener( tab );
- initCaretParagraphListener( tab );
- initKeyboardEventListeners( tab );
-// initSyntaxListener( tab );
- }
- }
- }
- }
- );
- }
-
- /**
- * Reloads the preferences from the previous session.
- */
- private void initPreferences() {
- restoreDefinitionSource();
- getFileEditorPane().restorePreferences();
- updateDefinitionPane();
- }
-
- /**
- * Listen for new tab selection events.
- */
- private void initTabChangedListener() {
- final FileEditorTabPane editorPane = getFileEditorPane();
-
- // Update the preview pane changing tabs.
- editorPane.addTabSelectionListener(
- (ObservableValue<? extends Tab> tabPane,
- final Tab oldTab, final Tab newTab) -> {
- updateVariableNameInjector();
-
- // If there was no old tab, then this is a first time load, which
- // can be ignored.
- if( oldTab != null ) {
- if( newTab == null ) {
- closeRemainingTab();
- }
- else {
- // Update the preview with the edited text.
- refreshSelectedTab( (FileEditorTab)newTab );
- }
- }
- }
- );
- }
-
- /**
- * Ensure that the keyboard events are received when a new tab is added
- * to the user interface.
- *
- * @param tab The tab that can trigger keyboard events, such as control+space.
- */
- private void initKeyboardEventListeners( final FileEditorTab tab ) {
- final VariableNameInjector vin = getVariableNameInjector();
- vin.initKeyboardEventListeners( tab );
- }
-
- private void initTextChangeListener( final FileEditorTab tab ) {
- tab.addTextChangeListener(
- (ObservableValue<? extends String> editor,
- final String oldValue, final String newValue) -> {
- refreshSelectedTab( tab );
- }
- );
- }
-
- private void initCaretParagraphListener( final FileEditorTab tab ) {
- tab.addCaretParagraphListener(
- (ObservableValue<? extends Integer> editor,
- final Integer oldValue, final Integer newValue) -> {
- refreshSelectedTab( tab );
- }
- );
- }
-
- private void updateVariableNameInjector() {
- getVariableNameInjector().setFileEditorTab( getActiveFileEditor() );
- }
-
- private void setVariableNameInjector( final VariableNameInjector injector ) {
- this.variableNameInjector = injector;
- }
-
- private synchronized VariableNameInjector getVariableNameInjector() {
- if( this.variableNameInjector == null ) {
- final VariableNameInjector vin = createVariableNameInjector();
- setVariableNameInjector( vin );
- }
-
- return this.variableNameInjector;
- }
-
- private VariableNameInjector createVariableNameInjector() {
- final FileEditorTab tab = getActiveFileEditor();
- final DefinitionPane pane = getDefinitionPane();
-
- return new VariableNameInjector( tab, pane );
- }
-
- /**
- * Add a listener for variable name injection the given tab.
- *
- * @param tab The tab to inject variable names into upon a double-click.
- */
- private void initVariableNameInjector( final Tab tab ) {
- final FileEditorTab editorTab = (FileEditorTab)tab;
- }
-
- /**
- * Called whenever the preview pane becomes out of sync with the file editor
- * tab. This can be called when the text changes, the caret paragraph changes,
- * or the file tab changes.
- *
- * @param tab The file editor tab that has been changed in some fashion.
- */
- private void refreshSelectedTab( final FileEditorTab tab ) {
- if( tab.isFileOpen() ) {
- getPreviewPane().setPath( tab.getPath() );
-
- // TODO: https://github.com/DaveJarvis/scrivenvar/issues/29
- final Position p = tab.getCaretOffset();
- getLineNumberText().setText(
- get( STATUS_BAR_LINE,
- p.getMajor() + 1,
- p.getMinor() + 1,
- tab.getCaretPosition() + 1
- )
- );
-
- Processor<String> processor = getProcessors().get( tab );
-
- if( processor == null ) {
- processor = createProcessor( tab );
- getProcessors().put( tab, processor );
- }
-
- try {
- getNotifier().clear();
- processor.processChain( tab.getEditorText() );
- }
- catch( final Exception ex ) {
- error( ex );
- }
- }
- }
-
- /**
- * Used to find text in the active file editor window.
- */
- private void find() {
- final TextField input = getFindTextField();
- getStatusBar().setGraphic( input );
- input.requestFocus();
- }
-
- public void findNext() {
- getActiveFileEditor().searchNext( getFindTextField().getText() );
- }
-
- /**
- * Returns the variable map of interpolated definitions.
- *
- * @return A map to help dereference variables.
- */
- private Map<String, String> getResolvedMap() {
- return getDefinitionSource().getResolvedMap();
- }
-
- /**
- * Returns the root node for the hierarchical definition source.
- *
- * @return Data to display in the definition pane.
- */
- private TreeView<String> getTreeView() {
- try {
- return getDefinitionSource().asTreeView();
- }
- catch( Exception e ) {
- error( e );
- }
-
- // Slightly redundant as getDefinitionSource() might have returned an
- // empty definition source.
- return (new EmptyDefinitionSource()).asTreeView();
- }
-
- /**
- * Called when a definition source is opened.
- *
- * @param path Path to the definition source that was opened.
- */
- private void openDefinition( final Path path ) {
- try {
- final DefinitionSource ds = createDefinitionSource( path.toString() );
- setDefinitionSource( ds );
- storeDefinitionSource();
- updateDefinitionPane();
- }
- catch( final Exception e ) {
- error( e );
- }
- }
-
- private void updateDefinitionPane() {
- getDefinitionPane().setRoot( getDefinitionSource().asTreeView() );
- }
-
- private void restoreDefinitionSource() {
- final Preferences preferences = getPreferences();
- final String source = preferences.get( PERSIST_DEFINITION_SOURCE, null );
-
- // If there's no definition source set, don't try to load it.
- if( source != null ) {
- setDefinitionSource( createDefinitionSource( source ) );
- }
- }
-
- private void storeDefinitionSource() {
- final Preferences preferences = getPreferences();
- final DefinitionSource ds = getDefinitionSource();
-
- preferences.put( PERSIST_DEFINITION_SOURCE, ds.toString() );
- }
-
- /**
- * Called when the last open tab is closed to clear the preview pane.
- */
- private void closeRemainingTab() {
- getPreviewPane().clear();
- }
-
- /**
- * Called when an exception occurs that warrants the user's attention.
- *
- * @param e The exception with a message that the user should know about.
- */
- private void error( final Exception e ) {
- getNotifier().notify( e );
- }
-
- //---- File actions -------------------------------------------------------
- /**
- * Called when an observable instance has changed. This is called by both the
- * snitch service and the notify service. The snitch service can be called for
- * different file types, including definition sources.
- *
- * @param observable The observed instance.
- * @param value The noteworthy item.
- */
- @Override
- public void update( final Observable observable, final Object value ) {
- if( value != null ) {
- if( observable instanceof Snitch && value instanceof Path ) {
- final Path path = (Path)value;
- final FileTypePredicate predicate
- = new FileTypePredicate( GLOB_DEFINITION_EXTENSIONS );
-
- // Reload definitions.
- if( predicate.test( path.toFile() ) ) {
- updateDefinitionSource( path );
- }
-
- updateSelectedTab();
- }
- else if( observable instanceof Notifier && value instanceof String ) {
- updateStatusBar( (String)value );
- }
- }
- }
-
- /**
- * Updates the status bar to show the given message.
- *
- * @param s The message to show in the status bar.
- */
- private void updateStatusBar( final String s ) {
- Platform.runLater(
- () -> {
- final int index = s.indexOf( '\n' );
- final String message = s.substring( 0, index > 0 ? index : s.length() );
-
- getStatusBar().setText( message );
- }
- );
- }
-
- /**
- * Called when a file has been modified.
- *
- * @param file Path to the modified file.
- */
- private void updateSelectedTab() {
- Platform.runLater(
- () -> {
- // Brute-force XSLT file reload by re-instantiating all processors.
- resetProcessors();
- refreshSelectedTab( getActiveFileEditor() );
- }
- );
- }
-
- /**
- * Reloads the definition source from the given path.
- *
- * @param path The path containing new definition information.
- */
- private void updateDefinitionSource( final Path path ) {
- Platform.runLater(
- () -> {
- openDefinition( path );
- }
- );
- }
-
- /**
- * After resetting the processors, they will refresh anew to be up-to-date
- * with the files (text and definition) currently loaded into the editor.
- */
- private void resetProcessors() {
- getProcessors().clear();
- }
-
- //---- File actions -------------------------------------------------------
- private void fileNew() {
- getFileEditorPane().newEditor();
- }
-
- private void fileOpen() {
- getFileEditorPane().openFileDialog();
- }
-
- private void fileClose() {
- getFileEditorPane().closeEditor( getActiveFileEditor(), true );
- }
-
- private void fileCloseAll() {
- getFileEditorPane().closeAllEditors();
- }
-
- private void fileSave() {
- getFileEditorPane().saveEditor( getActiveFileEditor() );
- }
-
- private void fileSaveAs() {
- final FileEditorTab editor = getActiveFileEditor();
- getFileEditorPane().saveEditorAs( editor );
- getProcessors().remove( editor );
-
- try {
- refreshSelectedTab( editor );
- }
- catch( final Exception ex ) {
- getNotifier().notify( ex );
- }
- }
-
- private void fileSaveAll() {
- getFileEditorPane().saveAllEditors();
- }
-
- private void fileExit() {
- final Window window = getWindow();
- fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
- }
-
- //---- Tools actions
- private void toolsScript() {
- final String script = getStartupScript();
-
- final RScriptDialog dialog = new RScriptDialog(
- getWindow(), "Dialog.rScript.title", script );
- final Optional<String> result = dialog.showAndWait();
-
- result.ifPresent( (String s) -> {
- putStartupScript( s );
- } );
+import com.scrivenvar.definition.*;
+import com.scrivenvar.dialogs.RScriptDialog;
+import com.scrivenvar.editors.EditorPane;
+import com.scrivenvar.editors.VariableNameInjector;
+import com.scrivenvar.editors.markdown.MarkdownEditorPane;
+import com.scrivenvar.predicates.files.FileTypePredicate;
+import com.scrivenvar.preview.HTMLPreviewPane;
+import com.scrivenvar.processors.Processor;
+import com.scrivenvar.processors.ProcessorFactory;
+import com.scrivenvar.service.Options;
+import com.scrivenvar.service.Snitch;
+import com.scrivenvar.service.events.Notifier;
+import com.scrivenvar.util.Action;
+import com.scrivenvar.util.ActionUtils;
+import javafx.application.Platform;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.value.ObservableBooleanValue;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.ListChangeListener.Change;
+import javafx.collections.ObservableList;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.control.*;
+import javafx.scene.control.Alert.AlertType;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.text.Text;
+import javafx.stage.Window;
+import javafx.stage.WindowEvent;
+import org.controlsfx.control.StatusBar;
+import org.fxmisc.richtext.model.TwoDimensional.Position;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.Function;
+import java.util.prefs.Preferences;
+
+import static com.scrivenvar.Constants.*;
+import static com.scrivenvar.Messages.get;
+import static com.scrivenvar.util.StageState.*;
+import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;
+import static javafx.event.Event.fireEvent;
+import static javafx.scene.input.KeyCode.ESCAPE;
+import static javafx.scene.input.KeyEvent.CHAR_UNDEFINED;
+import static javafx.scene.input.KeyEvent.KEY_PRESSED;
+import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST;
+
+/**
+ * Main window containing a tab pane in the center for file editors.
+ *
+ * @author Karl Tauber and White Magic Software, Ltd.
+ */
+public class MainWindow implements Observer {
+
+ private final Options options = Services.load( Options.class );
+ private final Snitch snitch = Services.load( Snitch.class );
+ private final Notifier notifier = Services.load( Notifier.class );
+
+ private Scene scene;
+ private MenuBar menuBar;
+ private StatusBar statusBar;
+ private Text lineNumberText;
+ private TextField findTextField;
+
+ private DefinitionSource definitionSource;
+ private DefinitionPane definitionPane;
+ private FileEditorTabPane fileEditorPane;
+ private HTMLPreviewPane previewPane;
+
+ /**
+ * Prevents re-instantiation of processing classes.
+ */
+ private Map<FileEditorTab, Processor<String>> processors;
+
+ /**
+ * Listens on the definition pane for double-click events.
+ */
+ private VariableNameInjector variableNameInjector;
+
+ public MainWindow() {
+ initLayout();
+ initFindInput();
+ initSnitch();
+ initDefinitionListener();
+ initTabAddedListener();
+ initTabChangedListener();
+ initPreferences();
+ }
+
+ /**
+ * Watch for changes to external files. In particular, this awaits
+ * modifications to any XSL files associated with XML files being edited. When
+ * an XSL file is modified (external to the application), the snitch's ears
+ * perk up and the file is reloaded. This keeps the XSL transformation up to
+ * date with what's on the file system.
+ */
+ private void initSnitch() {
+ getSnitch().addObserver( this );
+ }
+
+ /**
+ * Initialize the find input text field to listen on F3, ENTER, and ESCAPE key
+ * presses.
+ */
+ private void initFindInput() {
+ final TextField input = getFindTextField();
+
+ input.setOnKeyPressed( (KeyEvent event) -> {
+ switch( event.getCode() ) {
+ case F3:
+ case ENTER:
+ findNext();
+ break;
+ case F:
+ if( !event.isControlDown() ) {
+ break;
+ }
+ case ESCAPE:
+ getStatusBar().setGraphic( null );
+ getActiveFileEditor().getEditorPane().requestFocus();
+ break;
+ }
+ } );
+
+ // Remove when the input field loses focus.
+ input.focusedProperty().addListener(
+ (
+ final ObservableValue<? extends Boolean> focused,
+ final Boolean oFocus,
+ final Boolean nFocus) -> {
+ if( !nFocus ) {
+ getStatusBar().setGraphic( null );
+ }
+ }
+ );
+ }
+
+ /**
+ * Listen for file editor tab pane to receive an open definition source event.
+ */
+ private void initDefinitionListener() {
+ getFileEditorPane().onOpenDefinitionFileProperty().addListener(
+ (ObservableValue<? extends Path> definitionFile,
+ final Path oldPath, final Path newPath) -> {
+ openDefinition( newPath );
+
+ // Indirectly refresh the resolved map.
+ setProcessors( null );
+ updateDefinitionPane();
+
+ try {
+ getSnitch().ignore( oldPath );
+ getSnitch().listen( newPath );
+ }
+ catch( final IOException ex ) {
+ error( ex );
+ }
+
+ // Will create new processors and therefore a new resolved map.
+ refreshSelectedTab( getActiveFileEditor() );
+ }
+ );
+ }
+
+ /**
+ * When tabs are added, hook the various change listeners onto the new tab so
+ * that the preview pane refreshes as necessary.
+ */
+ private void initTabAddedListener() {
+ final FileEditorTabPane editorPane = getFileEditorPane();
+
+ // Make sure the text processor kicks off when new files are opened.
+ final ObservableList<Tab> tabs = editorPane.getTabs();
+
+ // Update the preview pane on tab changes.
+ tabs.addListener(
+ (final Change<? extends Tab> change) -> {
+ while( change.next() ) {
+ if( change.wasAdded() ) {
+ // Multiple tabs can be added simultaneously.
+ for( final Tab newTab : change.getAddedSubList() ) {
+ final FileEditorTab tab = (FileEditorTab)newTab;
+
+ initTextChangeListener( tab );
+ initCaretParagraphListener( tab );
+ initKeyboardEventListeners( tab );
+// initSyntaxListener( tab );
+ }
+ }
+ }
+ }
+ );
+ }
+
+ /**
+ * Reloads the preferences from the previous session.
+ */
+ private void initPreferences() {
+ restoreDefinitionSource();
+ getFileEditorPane().restorePreferences();
+ updateDefinitionPane();
+ }
+
+ /**
+ * Listen for new tab selection events.
+ */
+ private void initTabChangedListener() {
+ final FileEditorTabPane editorPane = getFileEditorPane();
+
+ // Update the preview pane changing tabs.
+ editorPane.addTabSelectionListener(
+ (ObservableValue<? extends Tab> tabPane,
+ final Tab oldTab, final Tab newTab) -> {
+ updateVariableNameInjector();
+
+ // If there was no old tab, then this is a first time load, which
+ // can be ignored.
+ if( oldTab != null ) {
+ if( newTab == null ) {
+ closeRemainingTab();
+ }
+ else {
+ // Update the preview with the edited text.
+ refreshSelectedTab( (FileEditorTab)newTab );
+ }
+ }
+ }
+ );
+ }
+
+ /**
+ * Ensure that the keyboard events are received when a new tab is added
+ * to the user interface.
+ *
+ * @param tab The tab that can trigger keyboard events, such as control+space.
+ */
+ private void initKeyboardEventListeners( final FileEditorTab tab ) {
+ final VariableNameInjector vin = getVariableNameInjector();
+ vin.initKeyboardEventListeners( tab );
+ }
+
+ private void initTextChangeListener( final FileEditorTab tab ) {
+ tab.addTextChangeListener(
+ (ObservableValue<? extends String> editor,
+ final String oldValue, final String newValue) -> {
+ refreshSelectedTab( tab );
+ }
+ );
+ }
+
+ private void initCaretParagraphListener( final FileEditorTab tab ) {
+ tab.addCaretParagraphListener(
+ (ObservableValue<? extends Integer> editor,
+ final Integer oldValue, final Integer newValue) -> {
+ refreshSelectedTab( tab );
+ }
+ );
+ }
+
+ private void updateVariableNameInjector() {
+ getVariableNameInjector().setFileEditorTab( getActiveFileEditor() );
+ }
+
+ private void setVariableNameInjector( final VariableNameInjector injector ) {
+ this.variableNameInjector = injector;
+ }
+
+ private synchronized VariableNameInjector getVariableNameInjector() {
+ if( this.variableNameInjector == null ) {
+ final VariableNameInjector vin = createVariableNameInjector();
+ setVariableNameInjector( vin );
+ }
+
+ return this.variableNameInjector;
+ }
+
+ private VariableNameInjector createVariableNameInjector() {
+ final FileEditorTab tab = getActiveFileEditor();
+ final DefinitionPane pane = getDefinitionPane();
+
+ return new VariableNameInjector( tab, pane );
+ }
+
+ /**
+ * Add a listener for variable name injection the given tab.
+ *
+ * @param tab The tab to inject variable names into upon a double-click.
+ */
+ private void initVariableNameInjector( final Tab tab ) {
+ final FileEditorTab editorTab = (FileEditorTab)tab;
+ }
+
+ /**
+ * Called whenever the preview pane becomes out of sync with the file editor
+ * tab. This can be called when the text changes, the caret paragraph changes,
+ * or the file tab changes.
+ *
+ * @param tab The file editor tab that has been changed in some fashion.
+ */
+ private void refreshSelectedTab( final FileEditorTab tab ) {
+ if( tab.isFileOpen() ) {
+ getPreviewPane().setPath( tab.getPath() );
+
+ // TODO: https://github.com/DaveJarvis/scrivenvar/issues/29
+ final Position p = tab.getCaretOffset();
+ getLineNumberText().setText(
+ get( STATUS_BAR_LINE,
+ p.getMajor() + 1,
+ p.getMinor() + 1,
+ tab.getCaretPosition() + 1
+ )
+ );
+
+ Processor<String> processor = getProcessors().get( tab );
+
+ if( processor == null ) {
+ processor = createProcessor( tab );
+ getProcessors().put( tab, processor );
+ }
+
+ try {
+ getNotifier().clear();
+ processor.processChain( tab.getEditorText() );
+ }
+ catch( final Exception ex ) {
+ error( ex );
+ }
+ }
+ }
+
+ /**
+ * Used to find text in the active file editor window.
+ */
+ private void find() {
+ final TextField input = getFindTextField();
+ getStatusBar().setGraphic( input );
+ input.requestFocus();
+ }
+
+ public void findNext() {
+ getActiveFileEditor().searchNext( getFindTextField().getText() );
+ }
+
+ /**
+ * Returns the variable map of interpolated definitions.
+ *
+ * @return A map to help dereference variables.
+ */
+ private Map<String, String> getResolvedMap() {
+ return getDefinitionSource().getResolvedMap();
+ }
+
+ /**
+ * Returns the root node for the hierarchical definition source.
+ *
+ * @return Data to display in the definition pane.
+ */
+ private TreeView<String> getTreeView() {
+ try {
+ return getDefinitionSource().asTreeView();
+ }
+ catch( Exception e ) {
+ error( e );
+ }
+
+ // Slightly redundant as getDefinitionSource() might have returned an
+ // empty definition source.
+ return (new EmptyDefinitionSource()).asTreeView();
+ }
+
+ /**
+ * Called when a definition source is opened.
+ *
+ * @param path Path to the definition source that was opened.
+ */
+ private void openDefinition( final Path path ) {
+ try {
+ final DefinitionSource ds = createDefinitionSource( path.toString() );
+ setDefinitionSource( ds );
+ storeDefinitionSource();
+ updateDefinitionPane();
+ }
+ catch( final Exception e ) {
+ error( e );
+ }
+ }
+
+ private void updateDefinitionPane() {
+ getDefinitionPane().setRoot( getDefinitionSource().asTreeView() );
+ }
+
+ private void restoreDefinitionSource() {
+ final Preferences preferences = getPreferences();
+ final String source = preferences.get( PERSIST_DEFINITION_SOURCE, null );
+
+ // If there's no definition source set, don't try to load it.
+ if( source != null ) {
+ setDefinitionSource( createDefinitionSource( source ) );
+ }
+ }
+
+ private void storeDefinitionSource() {
+ final Preferences preferences = getPreferences();
+ final DefinitionSource ds = getDefinitionSource();
+
+ preferences.put( PERSIST_DEFINITION_SOURCE, ds.toString() );
+ }
+
+ /**
+ * Called when the last open tab is closed to clear the preview pane.
+ */
+ private void closeRemainingTab() {
+ getPreviewPane().clear();
+ }
+
+ /**
+ * Called when an exception occurs that warrants the user's attention.
+ *
+ * @param e The exception with a message that the user should know about.
+ */
+ private void error( final Exception e ) {
+ getNotifier().notify( e );
+ }
+
+ //---- File actions -------------------------------------------------------
+ /**
+ * Called when an observable instance has changed. This is called by both the
+ * snitch service and the notify service. The snitch service can be called for
+ * different file types, including definition sources.
+ *
+ * @param observable The observed instance.
+ * @param value The noteworthy item.
+ */
+ @Override
+ public void update( final Observable observable, final Object value ) {
+ if( value != null ) {
+ if( observable instanceof Snitch && value instanceof Path ) {
+ final Path path = (Path)value;
+ final FileTypePredicate predicate
+ = new FileTypePredicate( GLOB_DEFINITION_EXTENSIONS );
+
+ // Reload definitions.
+ if( predicate.test( path.toFile() ) ) {
+ updateDefinitionSource( path );
+ }
+
+ updateSelectedTab();
+ }
+ else if( observable instanceof Notifier && value instanceof String ) {
+ updateStatusBar( (String)value );
+ }
+ }
+ }
+
+ /**
+ * Updates the status bar to show the given message.
+ *
+ * @param s The message to show in the status bar.
+ */
+ private void updateStatusBar( final String s ) {
+ Platform.runLater(
+ () -> {
+ final int index = s.indexOf( '\n' );
+ final String message = s.substring( 0, index > 0 ? index : s.length() );
+
+ getStatusBar().setText( message );
+ }
+ );
+ }
+
+ /**
+ * Called when a file has been modified.
+ */
+ private void updateSelectedTab() {
+ Platform.runLater(
+ () -> {
+ // Brute-force XSLT file reload by re-instantiating all processors.
+ resetProcessors();
+ refreshSelectedTab( getActiveFileEditor() );
+ }
+ );
+ }
+
+ /**
+ * Reloads the definition source from the given path.
+ *
+ * @param path The path containing new definition information.
+ */
+ private void updateDefinitionSource( final Path path ) {
+ Platform.runLater(
+ () -> {
+ openDefinition( path );
+ }
+ );
+ }
+
+ /**
+ * After resetting the processors, they will refresh anew to be up-to-date
+ * with the files (text and definition) currently loaded into the editor.
+ */
+ private void resetProcessors() {
+ getProcessors().clear();
+ }
+
+ //---- File actions -------------------------------------------------------
+ private void fileNew() {
+ getFileEditorPane().newEditor();
+ }
+
+ private void fileOpen() {
+ getFileEditorPane().openFileDialog();
+ }
+
+ private void fileClose() {
+ getFileEditorPane().closeEditor( getActiveFileEditor(), true );
+ }
+
+ private void fileCloseAll() {
+ getFileEditorPane().closeAllEditors();
+ }
+
+ private void fileSave() {
+ getFileEditorPane().saveEditor( getActiveFileEditor() );
+ }
+
+ private void fileSaveAs() {
+ final FileEditorTab editor = getActiveFileEditor();
+ getFileEditorPane().saveEditorAs( editor );
+ getProcessors().remove( editor );
+
+ try {
+ refreshSelectedTab( editor );
+ }
+ catch( final Exception ex ) {
+ getNotifier().notify( ex );
+ }
+ }
+
+ private void fileSaveAll() {
+ getFileEditorPane().saveAllEditors();
+ }
+
+ private void fileExit() {
+ final Window window = getWindow();
+ fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
+ }
+
+ //---- Tools actions
+ private void toolsScript() {
+ final String script = getStartupScript();
+
+ final RScriptDialog dialog = new RScriptDialog(
+ getWindow(), "Dialog.rScript.title", script );
+ final Optional<String> result = dialog.showAndWait();
+
+ result.ifPresent( this::putStartupScript );
}
libs/fontawesomefx-fontawesome-4.5.0.jar
Binary files differ