| | import static com.scrivenvar.Messages.get; |
| | import com.scrivenvar.definition.DefinitionPane; |
| | -import com.scrivenvar.editor.EditorPane; |
| | -import com.scrivenvar.editor.MarkdownEditorPane; |
| | -import com.scrivenvar.editor.VariableNameInjector; |
| | -import com.scrivenvar.preview.HTMLPreviewPane; |
| | -import com.scrivenvar.processors.HTMLPreviewProcessor; |
| | -import com.scrivenvar.processors.MarkdownCaretInsertionProcessor; |
| | -import com.scrivenvar.processors.MarkdownCaretReplacementProcessor; |
| | -import com.scrivenvar.processors.MarkdownProcessor; |
| | -import com.scrivenvar.processors.Processor; |
| | -import com.scrivenvar.processors.TextChangeProcessor; |
| | -import com.scrivenvar.processors.VariableProcessor; |
| | -import com.scrivenvar.service.Options; |
| | -import com.scrivenvar.util.Action; |
| | -import com.scrivenvar.util.ActionUtils; |
| | -import static com.scrivenvar.util.StageState.K_PANE_SPLIT_DEFINITION; |
| | -import static com.scrivenvar.util.StageState.K_PANE_SPLIT_EDITOR; |
| | -import static com.scrivenvar.util.StageState.K_PANE_SPLIT_PREVIEW; |
| | -import com.scrivenvar.yaml.YamlParser; |
| | -import com.scrivenvar.yaml.YamlTreeAdapter; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.BOLD; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.CODE; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_ALT; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_CODE_ALT; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FLOPPY_ALT; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FOLDER_OPEN_ALT; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.HEADER; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.ITALIC; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LINK; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_OL; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_UL; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.PICTURE_ALT; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.QUOTE_LEFT; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.REPEAT; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.STRIKETHROUGH; |
| | -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.UNDO; |
| | -import java.io.IOException; |
| | -import java.io.InputStream; |
| | -import java.util.Map; |
| | -import java.util.function.Function; |
| | -import java.util.prefs.Preferences; |
| | -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.event.Event; |
| | -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.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.stage.Window; |
| | -import javafx.stage.WindowEvent; |
| | -import org.fxmisc.richtext.StyleClassedTextArea; |
| | - |
| | -/** |
| | - * Main window containing a tab pane in the center for file editors. |
| | - * |
| | - * @author Karl Tauber and White Magic Software, Ltd. |
| | - */ |
| | -public class MainWindow { |
| | - |
| | - private final Options options = Services.load( Options.class ); |
| | - |
| | - private Scene scene; |
| | - |
| | - private TreeView<String> treeView; |
| | - private DefinitionPane definitionPane; |
| | - private FileEditorTabPane fileEditorPane; |
| | - private HTMLPreviewPane previewPane; |
| | - |
| | - private VariableNameInjector variableNameInjector; |
| | - |
| | - private YamlTreeAdapter yamlTreeAdapter; |
| | - private YamlParser yamlParser; |
| | - |
| | - private MenuBar menuBar; |
| | - |
| | - public MainWindow() { |
| | - initLayout(); |
| | - initTabsListener(); |
| | - restorePreferences(); |
| | - initEditorPaneListeners(); |
| | - initVariableNameInjector(); |
| | - } |
| | - |
| | - private void initLayout() { |
| | - final SplitPane splitPane = new SplitPane( |
| | - getDefinitionPane().getNode(), |
| | - getFileEditorPane().getNode(), |
| | - getPreviewPane().getNode() ); |
| | - |
| | - splitPane.setDividerPositions( |
| | - getFloat( K_PANE_SPLIT_DEFINITION, .10f ), |
| | - getFloat( K_PANE_SPLIT_EDITOR, .45f ), |
| | - getFloat( K_PANE_SPLIT_PREVIEW, .45f ) ); |
| | - |
| | - // See: http://broadlyapplicable.blogspot.ca/2015/03/javafx-capture-restorePreferences-splitpane.html |
| | - final BorderPane borderPane = new BorderPane(); |
| | - borderPane.setPrefSize( 1024, 800 ); |
| | - borderPane.setTop( createMenuBar() ); |
| | - borderPane.setCenter( splitPane ); |
| | - |
| | - final Scene appScene = new Scene( borderPane ); |
| | - setScene( appScene ); |
| | - appScene.getStylesheets().add( Constants.STYLESHEET_PREVIEW ); |
| | - appScene.windowProperty().addListener( |
| | - (observable, oldWindow, newWindow) -> { |
| | - newWindow.setOnCloseRequest( e -> { |
| | - if( !getFileEditorPane().closeAllEditors() ) { |
| | - e.consume(); |
| | - } |
| | - } ); |
| | - |
| | - // Workaround JavaFX bug: deselect menubar if window loses focus. |
| | - newWindow.focusedProperty().addListener( |
| | - (obs, oldFocused, newFocused) -> { |
| | - if( !newFocused ) { |
| | - // Send an ESC key event to the menubar |
| | - this.menuBar.fireEvent( |
| | - new KeyEvent( |
| | - KEY_PRESSED, CHAR_UNDEFINED, "", ESCAPE, |
| | - false, false, false, false ) ); |
| | - } |
| | - } ); |
| | - } ); |
| | - } |
| | - |
| | - private void initVariableNameInjector() { |
| | - setVariableNameInjector( new VariableNameInjector( |
| | - getFileEditorPane(), |
| | - getDefinitionPane() ) |
| | - ); |
| | - } |
| | - |
| | - private Window getWindow() { |
| | - return getScene().getWindow(); |
| | - } |
| | - |
| | - public Scene getScene() { |
| | - return this.scene; |
| | - } |
| | - |
| | - private void setScene( Scene scene ) { |
| | - this.scene = scene; |
| | - } |
| | - |
| | - /** |
| | - * Creates a boolean property that is bound to another boolean value of the |
| | - * active editor. |
| | - */ |
| | - private BooleanProperty createActiveBooleanProperty( |
| | - final Function<FileEditorTab, ObservableBooleanValue> func ) { |
| | - |
| | - final BooleanProperty b = new SimpleBooleanProperty(); |
| | - final FileEditorTab tab = getActiveFileEditor(); |
| | - |
| | - if( tab != null ) { |
| | - b.bind( func.apply( tab ) ); |
| | - } |
| | - |
| | - getFileEditorPane().activeFileEditorProperty().addListener( |
| | - (observable, oldFileEditor, newFileEditor) -> { |
| | - b.unbind(); |
| | - |
| | - if( newFileEditor != null ) { |
| | - b.bind( func.apply( newFileEditor ) ); |
| | - } else { |
| | - b.set( false ); |
| | - } |
| | - } ); |
| | - |
| | - return b; |
| | - } |
| | - |
| | - //---- 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 fileSaveAll() { |
| | - getFileEditorPane().saveAllEditors(); |
| | - } |
| | - |
| | - private void fileExit() { |
| | - final Window window = getWindow(); |
| | - Event.fireEvent( window, |
| | - new WindowEvent( window, WindowEvent.WINDOW_CLOSE_REQUEST ) ); |
| | - } |
| | - |
| | - //---- Help actions ------------------------------------------------------- |
| | - private void helpAbout() { |
| | - Alert alert = new Alert( AlertType.INFORMATION ); |
| | - alert.setTitle( Messages.get( "Dialog.about.title" ) ); |
| | - alert.setHeaderText( Messages.get( "Dialog.about.header" ) ); |
| | - alert.setContentText( Messages.get( "Dialog.about.content" ) ); |
| | - alert.setGraphic( new ImageView( new Image( LOGO_32 ) ) ); |
| | - alert.initOwner( getWindow() ); |
| | - |
| | - alert.showAndWait(); |
| | - } |
| | - |
| | - private FileEditorTabPane getFileEditorPane() { |
| | - if( this.fileEditorPane == null ) { |
| | - this.fileEditorPane = createFileEditorPane(); |
| | - } |
| | - |
| | - return this.fileEditorPane; |
| | - } |
| | - |
| | - /** |
| | - * Create an editor pane to hold file editor tabs. |
| | - * |
| | - * @return A new instance, never null. |
| | - */ |
| | - private FileEditorTabPane createFileEditorPane() { |
| | - return new FileEditorTabPane(); |
| | - } |
| | - |
| | - /** |
| | - * Reloads the preferences from the previous load. |
| | - */ |
| | - private void restorePreferences() { |
| | - getFileEditorPane().restorePreferences(); |
| | - } |
| | - |
| | - private void initTabsListener() { |
| | - 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; |
| | - |
| | - refresh( tab ); |
| | - } |
| | - } |
| | - } |
| | - } ); |
| | - } |
| | - |
| | - private void initEditorPaneListeners() { |
| | - final FileEditorTabPane editorPane = getFileEditorPane(); |
| | - |
| | - // Update the preview pane when moving the caret to a new paragraph. |
| | - editorPane.addTabChangeListener( |
| | - (ObservableValue<? extends Tab> tabPane, |
| | - final Tab oldTab, final Tab newTab) -> { |
| | - |
| | - final FileEditorTab tab = (FileEditorTab)newTab; |
| | - |
| | - if( tab != null ) { |
| | - getPreviewPane().setPath( tab.getPath() ); |
| | - refresh( tab ); |
| | - } |
| | - } ); |
| | - |
| | - editorPane.getEditor().textProperty().addListener( (ov, oldv, newv) -> { |
| | - refresh( getActiveFileEditor() ); |
| | - } ); |
| | - } |
| | - |
| | - private void refresh( final FileEditorTab tab ) { |
| | - System.out.println( "REFRESH: " + tab.getPath().toAbsolutePath() ); |
| | - } |
| | - |
| | - private MarkdownEditorPane getActiveEditor() { |
| | - return (MarkdownEditorPane)(getActiveFileEditor().getEditorPane()); |
| | - } |
| | - |
| | - private FileEditorTab getActiveFileEditor() { |
| | - return getFileEditorPane().getActiveFileEditor(); |
| | - } |
| | - |
| | - private Processor<String> createVariableProcessor( final FileEditorTab tab ) { |
| | - final HTMLPreviewPane previewPanel = getPreviewPane(); |
| | - final EditorPane editorPanel = tab.getEditorPane(); |
| | - final StyleClassedTextArea editor = editorPanel.getEditor(); |
| | - |
| | - // TODO: Use a factory based on the filename extension. The default |
| | - // extension will be for a markdown file (e.g., on file new). |
| | - final Processor<String> hpp = new HTMLPreviewProcessor( previewPanel ); |
| | - final Processor<String> mcrp = new MarkdownCaretReplacementProcessor( hpp ); |
| | - final Processor<String> mp = new MarkdownProcessor( mcrp ); |
| | - final Processor<String> mcip = new MarkdownCaretInsertionProcessor( mp, editor.caretPositionProperty() ); |
| | - final Processor<String> vp = new VariableProcessor( mcip, getResolvedMap() ); |
| | - |
| | - return vp; |
| | - } |
| | - |
| | - private TextChangeProcessor createTextChangeProcessor( |
| | - final Processor<String> link ) { |
| | - return new TextChangeProcessor( link ); |
| | - } |
| | - |
| | - /** |
| | - * Listens for changes to tabs and their text editors. |
| | - * |
| | - * @see https://github.com/DaveJarvis/scrivenvar/issues/17 |
| | - * @see https://github.com/DaveJarvis/scrivenvar/issues/18 |
| | - * |
| | - * @param tab The file editor tab that contains a text editor. |
| | - */ |
| | - private void addListener( final FileEditorTab tab ) { |
| | - final Processor<String> vnp = createVariableProcessor( tab ); |
| | - final TextChangeProcessor tcp = createTextChangeProcessor( vnp ); |
| | - |
| | - addCaretParagraphListener( tab, vnp ); |
| | - } |
| | - |
| | - /** |
| | - * When the caret changes paragraph, force re-rendering of the preview panel |
| | - * using the chain-of-command. |
| | - * |
| | - * @param tab Contains a text editor to monitor for caret position changes. |
| | - * @param vnp Called to re-process chain using the editor's content. |
| | - */ |
| | - private void addCaretParagraphListener( final FileEditorTab tab, final Processor<String> vnp ) { |
| | - final EditorPane editorPanel = tab.getEditorPane(); |
| | - final StyleClassedTextArea editor = editorPanel.getEditor(); |
| | - |
| | - editorPanel.addCaretParagraphListener( |
| | - (final ObservableValue<? extends Integer> observable, |
| | - final Integer oldValue, final Integer newValue) -> { |
| | - |
| | - // Kick off the processing chain at the variable processor when the |
| | - // cursor changes paragraphs. This might cause some slight duplication |
| | - // when the Enter key is pressed. |
| | - vnp.processChain( editor.getText() ); |
| | - } ); |
| | - } |
| | - |
| | - protected DefinitionPane createDefinitionPane() { |
| | - return new DefinitionPane( getTreeView() ); |
| | - } |
| | - |
| | - private DefinitionPane getDefinitionPane() { |
| | - if( this.definitionPane == null ) { |
| | - this.definitionPane = createDefinitionPane(); |
| | - } |
| | - |
| | - return this.definitionPane; |
| | - } |
| | - |
| | - public MenuBar getMenuBar() { |
| | - return menuBar; |
| | +import com.scrivenvar.editor.MarkdownEditorPane; |
| | +import com.scrivenvar.editor.VariableNameInjector; |
| | +import com.scrivenvar.preview.HTMLPreviewPane; |
| | +import com.scrivenvar.processors.HTMLPreviewProcessor; |
| | +import com.scrivenvar.processors.MarkdownCaretInsertionProcessor; |
| | +import com.scrivenvar.processors.MarkdownCaretReplacementProcessor; |
| | +import com.scrivenvar.processors.MarkdownProcessor; |
| | +import com.scrivenvar.processors.Processor; |
| | +import com.scrivenvar.processors.VariableProcessor; |
| | +import com.scrivenvar.service.Options; |
| | +import com.scrivenvar.util.Action; |
| | +import com.scrivenvar.util.ActionUtils; |
| | +import static com.scrivenvar.util.StageState.K_PANE_SPLIT_DEFINITION; |
| | +import static com.scrivenvar.util.StageState.K_PANE_SPLIT_EDITOR; |
| | +import static com.scrivenvar.util.StageState.K_PANE_SPLIT_PREVIEW; |
| | +import com.scrivenvar.yaml.YamlParser; |
| | +import com.scrivenvar.yaml.YamlTreeAdapter; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.BOLD; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.CODE; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_ALT; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_CODE_ALT; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FLOPPY_ALT; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FOLDER_OPEN_ALT; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.HEADER; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.ITALIC; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LINK; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_OL; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_UL; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.PICTURE_ALT; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.QUOTE_LEFT; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.REPEAT; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.STRIKETHROUGH; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.UNDO; |
| | +import java.io.IOException; |
| | +import java.io.InputStream; |
| | +import java.util.Map; |
| | +import java.util.function.Function; |
| | +import java.util.prefs.Preferences; |
| | +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.event.Event; |
| | +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.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.stage.Window; |
| | +import javafx.stage.WindowEvent; |
| | + |
| | +/** |
| | + * Main window containing a tab pane in the center for file editors. |
| | + * |
| | + * @author Karl Tauber and White Magic Software, Ltd. |
| | + */ |
| | +public class MainWindow { |
| | + |
| | + private final Options options = Services.load( Options.class ); |
| | + |
| | + private Scene scene; |
| | + |
| | + private TreeView<String> treeView; |
| | + private DefinitionPane definitionPane; |
| | + private FileEditorTabPane fileEditorPane; |
| | + private HTMLPreviewPane previewPane; |
| | + |
| | + private VariableNameInjector variableNameInjector; |
| | + |
| | + private YamlTreeAdapter yamlTreeAdapter; |
| | + private YamlParser yamlParser; |
| | + |
| | + private MenuBar menuBar; |
| | + |
| | + public MainWindow() { |
| | + initLayout(); |
| | + initTabAddedListener(); |
| | + restorePreferences(); |
| | + initTabChangeListener(); |
| | + initVariableNameInjector(); |
| | + } |
| | + |
| | + private void initLayout() { |
| | + final SplitPane splitPane = new SplitPane( |
| | + getDefinitionPane().getNode(), |
| | + getFileEditorPane().getNode(), |
| | + getPreviewPane().getNode() ); |
| | + |
| | + splitPane.setDividerPositions( |
| | + getFloat( K_PANE_SPLIT_DEFINITION, .10f ), |
| | + getFloat( K_PANE_SPLIT_EDITOR, .45f ), |
| | + getFloat( K_PANE_SPLIT_PREVIEW, .45f ) ); |
| | + |
| | + // See: http://broadlyapplicable.blogspot.ca/2015/03/javafx-capture-restorePreferences-splitpane.html |
| | + final BorderPane borderPane = new BorderPane(); |
| | + borderPane.setPrefSize( 1024, 800 ); |
| | + borderPane.setTop( createMenuBar() ); |
| | + borderPane.setCenter( splitPane ); |
| | + |
| | + final Scene appScene = new Scene( borderPane ); |
| | + setScene( appScene ); |
| | + appScene.getStylesheets().add( Constants.STYLESHEET_PREVIEW ); |
| | + appScene.windowProperty().addListener( |
| | + (observable, oldWindow, newWindow) -> { |
| | + newWindow.setOnCloseRequest( e -> { |
| | + if( !getFileEditorPane().closeAllEditors() ) { |
| | + e.consume(); |
| | + } |
| | + } ); |
| | + |
| | + // Workaround JavaFX bug: deselect menubar if window loses focus. |
| | + newWindow.focusedProperty().addListener( |
| | + (obs, oldFocused, newFocused) -> { |
| | + if( !newFocused ) { |
| | + // Send an ESC key event to the menubar |
| | + this.menuBar.fireEvent( |
| | + new KeyEvent( |
| | + KEY_PRESSED, CHAR_UNDEFINED, "", ESCAPE, |
| | + false, false, false, false ) ); |
| | + } |
| | + } ); |
| | + } ); |
| | + } |
| | + |
| | + private void initVariableNameInjector() { |
| | + setVariableNameInjector( new VariableNameInjector( |
| | + getFileEditorPane(), |
| | + getDefinitionPane() ) |
| | + ); |
| | + } |
| | + |
| | + private Window getWindow() { |
| | + return getScene().getWindow(); |
| | + } |
| | + |
| | + public Scene getScene() { |
| | + return this.scene; |
| | + } |
| | + |
| | + private void setScene( Scene scene ) { |
| | + this.scene = scene; |
| | + } |
| | + |
| | + /** |
| | + * Creates a boolean property that is bound to another boolean value of the |
| | + * active editor. |
| | + */ |
| | + private BooleanProperty createActiveBooleanProperty( |
| | + final Function<FileEditorTab, ObservableBooleanValue> func ) { |
| | + |
| | + final BooleanProperty b = new SimpleBooleanProperty(); |
| | + final FileEditorTab tab = getActiveFileEditor(); |
| | + |
| | + if( tab != null ) { |
| | + b.bind( func.apply( tab ) ); |
| | + } |
| | + |
| | + getFileEditorPane().activeFileEditorProperty().addListener( |
| | + (observable, oldFileEditor, newFileEditor) -> { |
| | + b.unbind(); |
| | + |
| | + if( newFileEditor != null ) { |
| | + b.bind( func.apply( newFileEditor ) ); |
| | + } else { |
| | + b.set( false ); |
| | + } |
| | + } ); |
| | + |
| | + return b; |
| | + } |
| | + |
| | + //---- 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 fileSaveAll() { |
| | + getFileEditorPane().saveAllEditors(); |
| | + } |
| | + |
| | + private void fileExit() { |
| | + final Window window = getWindow(); |
| | + Event.fireEvent( window, |
| | + new WindowEvent( window, WindowEvent.WINDOW_CLOSE_REQUEST ) ); |
| | + } |
| | + |
| | + //---- Help actions ------------------------------------------------------- |
| | + private void helpAbout() { |
| | + Alert alert = new Alert( AlertType.INFORMATION ); |
| | + alert.setTitle( Messages.get( "Dialog.about.title" ) ); |
| | + alert.setHeaderText( Messages.get( "Dialog.about.header" ) ); |
| | + alert.setContentText( Messages.get( "Dialog.about.content" ) ); |
| | + alert.setGraphic( new ImageView( new Image( LOGO_32 ) ) ); |
| | + alert.initOwner( getWindow() ); |
| | + |
| | + alert.showAndWait(); |
| | + } |
| | + |
| | + private FileEditorTabPane getFileEditorPane() { |
| | + if( this.fileEditorPane == null ) { |
| | + this.fileEditorPane = createFileEditorPane(); |
| | + } |
| | + |
| | + return this.fileEditorPane; |
| | + } |
| | + |
| | + /** |
| | + * Create an editor pane to hold file editor tabs. |
| | + * |
| | + * @return A new instance, never null. |
| | + */ |
| | + private FileEditorTabPane createFileEditorPane() { |
| | + return new FileEditorTabPane(); |
| | + } |
| | + |
| | + /** |
| | + * Reloads the preferences from the previous load. |
| | + */ |
| | + private void restorePreferences() { |
| | + getFileEditorPane().restorePreferences(); |
| | + } |
| | + |
| | + 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 ); |
| | + process( tab ); |
| | + } |
| | + } |
| | + } |
| | + } ); |
| | + } |
| | + |
| | + /** |
| | + * Listen for tab changes. |
| | + */ |
| | + private void initTabChangeListener() { |
| | + final FileEditorTabPane editorPane = getFileEditorPane(); |
| | + |
| | + // Update the preview pane changing tabs. |
| | + editorPane.addTabChangeListener( |
| | + (ObservableValue<? extends Tab> tabPane, |
| | + final Tab oldTab, final Tab newTab) -> { |
| | + |
| | + final FileEditorTab tab = (FileEditorTab)newTab; |
| | + |
| | + if( tab != null ) { |
| | + // When a new tab is selected, ensure that the base path to images |
| | + // is set correctly. |
| | + getPreviewPane().setPath( tab.getPath() ); |
| | + process( tab ); |
| | + } |
| | + } ); |
| | + } |
| | + |
| | + private void initTextChangeListener( final FileEditorTab tab ) { |
| | + tab.addTextChangeListener( (ObservableValue<? extends String> editor, |
| | + final String oldValue, final String newValue) -> { |
| | + process( tab ); |
| | + } ); |
| | + } |
| | + |
| | + private void initCaretParagraphListener( final FileEditorTab tab ) { |
| | + tab.addCaretParagraphListener( (ObservableValue<? extends Integer> editor, |
| | + final Integer oldValue, final Integer newValue) -> { |
| | + process( 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 process( final FileEditorTab tab ) { |
| | + // TODO: Use a factory based on the filename extension. The default |
| | + // extension will be for a markdown file (e.g., on file new). |
| | + final Processor<String> hpp = new HTMLPreviewProcessor( getPreviewPane() ); |
| | + final Processor<String> mcrp = new MarkdownCaretReplacementProcessor( hpp ); |
| | + final Processor<String> mp = new MarkdownProcessor( mcrp ); |
| | + final Processor<String> mcip = new MarkdownCaretInsertionProcessor( mp, tab.getCaretPosition() ); |
| | + final Processor<String> vp = new VariableProcessor( mcip, getResolvedMap() ); |
| | + |
| | + vp.processChain( tab.getEditorText() ); |
| | + } |
| | + |
| | + private MarkdownEditorPane getActiveEditor() { |
| | + return (MarkdownEditorPane)(getActiveFileEditor().getEditorPane()); |
| | + } |
| | + |
| | + private FileEditorTab getActiveFileEditor() { |
| | + return getFileEditorPane().getActiveFileEditor(); |
| | + } |
| | + |
| | + protected DefinitionPane createDefinitionPane() { |
| | + return new DefinitionPane( getTreeView() ); |
| | + } |
| | + |
| | + private DefinitionPane getDefinitionPane() { |
| | + if( this.definitionPane == null ) { |
| | + this.definitionPane = createDefinitionPane(); |
| | + } |
| | + |
| | + return this.definitionPane; |
| | + } |
| | + |
| | + public MenuBar getMenuBar() { |
| | + return this.menuBar; |
| | } |
| | |