| | package com.scrivenvar; |
| | |
| | -import static com.scrivenvar.Constants.FILE_LOGO_32; |
| | -import static com.scrivenvar.Constants.PREFS_DEFINITION_SOURCE; |
| | -import static com.scrivenvar.Constants.STYLESHEET_SCENE; |
| | -import static com.scrivenvar.Messages.get; |
| | -import com.scrivenvar.definition.DefinitionFactory; |
| | -import com.scrivenvar.definition.DefinitionPane; |
| | -import com.scrivenvar.definition.DefinitionSource; |
| | -import com.scrivenvar.definition.EmptyDefinitionSource; |
| | -import com.scrivenvar.editors.EditorPane; |
| | -import com.scrivenvar.editors.VariableNameInjector; |
| | -import com.scrivenvar.editors.markdown.MarkdownEditorPane; |
| | -import com.scrivenvar.preview.HTMLPreviewPane; |
| | -import com.scrivenvar.processors.CaretReplacementProcessor; |
| | -import com.scrivenvar.processors.HTMLPreviewProcessor; |
| | -import com.scrivenvar.processors.MarkdownProcessor; |
| | -import com.scrivenvar.processors.Processor; |
| | -import com.scrivenvar.processors.VariableProcessor; |
| | -import com.scrivenvar.processors.XMLCaretInsertionProcessor; |
| | -import com.scrivenvar.processors.XMLProcessor; |
| | -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 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.net.MalformedURLException; |
| | -import java.nio.file.Path; |
| | -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 static javafx.event.Event.fireEvent; |
| | -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 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 { |
| | - |
| | - private final Options options = Services.load( Options.class ); |
| | - |
| | - private Scene scene; |
| | - private MenuBar menuBar; |
| | - |
| | - private DefinitionPane definitionPane; |
| | - private FileEditorTabPane fileEditorPane; |
| | - private HTMLPreviewPane previewPane; |
| | - |
| | - private DefinitionSource definitionSource; |
| | - |
| | - public MainWindow() { |
| | - initLayout(); |
| | - initOpenDefinitionListener(); |
| | - initTabAddedListener(); |
| | - initTabChangedListener(); |
| | - initPreferences(); |
| | - } |
| | - |
| | - /** |
| | - * Listen for file editor tab pane to receive an open definition source event. |
| | - */ |
| | - private void initOpenDefinitionListener() { |
| | - getFileEditorPane().onOpenDefinitionFileProperty().addListener( |
| | - (ObservableValue<? extends Path> definitionFile, |
| | - final Path oldPath, final Path newPath) -> { |
| | - openDefinition( newPath ); |
| | - 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 ); |
| | - initVariableNameInjector( tab ); |
| | - } |
| | - } |
| | - } |
| | - } |
| | - ); |
| | - } |
| | - |
| | - /** |
| | - * Reloads the preferences from the previous load. |
| | - */ |
| | - private void initPreferences() { |
| | - getFileEditorPane().restorePreferences(); |
| | - restoreDefinitionSource(); |
| | - } |
| | - |
| | - /** |
| | - * 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) -> { |
| | - |
| | - // 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 ); |
| | - } |
| | - } |
| | - } |
| | - ); |
| | - } |
| | - |
| | - 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 initVariableNameInjector( final FileEditorTab tab ) { |
| | - VariableNameInjector.listen( tab, getDefinitionPane() ); |
| | - } |
| | - |
| | - /** |
| | - * 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 ) { |
| | - final Path path = tab.getPath(); |
| | - |
| | - final HTMLPreviewPane preview = getPreviewPane(); |
| | - preview.setPath( tab.getPath() ); |
| | - |
| | - final Processor<String> hpp = new HTMLPreviewProcessor( preview ); |
| | - final Processor<String> mcrp = new CaretReplacementProcessor( hpp ); |
| | - final Processor<String> mp = new MarkdownProcessor( mcrp ); |
| | -// final Processor<String> mcip = new MarkdownCaretInsertionProcessor( mp, tab.getCaretPosition() ); |
| | - final Processor<String> xmlp = new XMLProcessor( mp, tab.getPath() ); |
| | - final Processor<String> xcip = new XMLCaretInsertionProcessor( xmlp, tab.getCaretPosition() ); |
| | - final Processor<String> vp = new VariableProcessor( xcip, getResolvedMap() ); |
| | - |
| | - vp.processChain( tab.getEditorText() ); |
| | - } |
| | - |
| | - /** |
| | - * 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 ) { |
| | - alert( e ); |
| | - } |
| | - |
| | - return new TreeView<>(); |
| | - } |
| | - |
| | - private void openDefinition( final Path path ) { |
| | - openDefinition( path.toString() ); |
| | - } |
| | - |
| | - private void openDefinition( final String path ) { |
| | - try { |
| | - final DefinitionSource ds = createDefinitionSource( path ); |
| | - setDefinitionSource( ds ); |
| | - storeDefinitionSource(); |
| | - |
| | - getDefinitionPane().setRoot( ds.asTreeView() ); |
| | - } catch( Exception e ) { |
| | - alert( e ); |
| | - } |
| | - } |
| | - |
| | - private void restoreDefinitionSource() { |
| | - final Preferences preferences = getPreferences(); |
| | - final String source = preferences.get( PREFS_DEFINITION_SOURCE, null ); |
| | - |
| | - if( source != null ) { |
| | - openDefinition( source ); |
| | - } |
| | - } |
| | - |
| | - private void storeDefinitionSource() { |
| | - final Preferences preferences = getPreferences(); |
| | - final DefinitionSource ds = getDefinitionSource(); |
| | - |
| | - preferences.put( PREFS_DEFINITION_SOURCE, ds.toString() ); |
| | - } |
| | - |
| | - /** |
| | - * Called when the last open tab is closed. This clears out the preview pane |
| | - * and the definition pane. |
| | - */ |
| | - private void closeRemainingTab() { |
| | - getPreviewPane().clear(); |
| | - getDefinitionPane().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 alert( final Exception e ) { |
| | - // TODO: Raise a notice. |
| | - } |
| | - |
| | - //---- 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(); |
| | - fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) ); |
| | - } |
| | - |
| | - //---- Help actions ------------------------------------------------------- |
| | - private void helpAbout() { |
| | - Alert alert = new Alert( AlertType.INFORMATION ); |
| | - alert.setTitle( get( "Dialog.about.title" ) ); |
| | - alert.setHeaderText( get( "Dialog.about.header" ) ); |
| | - alert.setContentText( get( "Dialog.about.content" ) ); |
| | - alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) ); |
| | - alert.initOwner( getWindow() ); |
| | - |
| | - alert.showAndWait(); |
| | - } |
| | - |
| | - //---- Convenience accessors ---------------------------------------------- |
| | - private float getFloat( final String key, final float defaultValue ) { |
| | - return getPreferences().getFloat( key, defaultValue ); |
| | - } |
| | - |
| | - private Preferences getPreferences() { |
| | - return getOptions().getState(); |
| | - } |
| | - |
| | - private Window getWindow() { |
| | - return getScene().getWindow(); |
| | - } |
| | - |
| | - private MarkdownEditorPane getActiveEditor() { |
| | - final EditorPane pane = getActiveFileEditor().getEditorPane(); |
| | - |
| | - return pane instanceof MarkdownEditorPane ? (MarkdownEditorPane)pane : null; |
| | - } |
| | - |
| | - private FileEditorTab getActiveFileEditor() { |
| | - return getFileEditorPane().getActiveFileEditor(); |
| | - } |
| | - |
| | - //---- Member accessors --------------------------------------------------- |
| | - public Scene getScene() { |
| | - return this.scene; |
| | - } |
| | - |
| | - private void setScene( Scene scene ) { |
| | - this.scene = scene; |
| | - } |
| | - |
| | - private FileEditorTabPane getFileEditorPane() { |
| | - if( this.fileEditorPane == null ) { |
| | - this.fileEditorPane = createFileEditorPane(); |
| | - } |
| | - |
| | - return this.fileEditorPane; |
| | - } |
| | - |
| | - private HTMLPreviewPane getPreviewPane() { |
| | - if( this.previewPane == null ) { |
| | - this.previewPane = createPreviewPane(); |
| | - } |
| | - |
| | - return this.previewPane; |
| | - } |
| | - |
| | - private void setDefinitionSource( final DefinitionSource definitionSource ) { |
| | - this.definitionSource = definitionSource; |
| | - } |
| | - |
| | - private DefinitionSource getDefinitionSource() { |
| | - if( this.definitionSource == null ) { |
| | - this.definitionSource = new EmptyDefinitionSource(); |
| | - } |
| | - |
| | - return this.definitionSource; |
| | - } |
| | - |
| | - private DefinitionPane getDefinitionPane() { |
| | - if( this.definitionPane == null ) { |
| | - this.definitionPane = createDefinitionPane(); |
| | - } |
| | - |
| | - return this.definitionPane; |
| | - } |
| | - |
| | - private Options getOptions() { |
| | - return this.options; |
| | - } |
| | - |
| | - public MenuBar getMenuBar() { |
| | - return this.menuBar; |
| | - } |
| | - |
| | - public void setMenuBar( MenuBar menuBar ) { |
| | - this.menuBar = menuBar; |
| | - } |
| | - |
| | - //---- Member creators ---------------------------------------------------- |
| | +import static com.scrivenvar.Constants.*; |
| | +import static com.scrivenvar.Messages.get; |
| | +import com.scrivenvar.definition.DefinitionFactory; |
| | +import com.scrivenvar.definition.DefinitionPane; |
| | +import com.scrivenvar.definition.DefinitionSource; |
| | +import com.scrivenvar.definition.EmptyDefinitionSource; |
| | +import com.scrivenvar.editors.EditorPane; |
| | +import com.scrivenvar.editors.VariableNameInjector; |
| | +import com.scrivenvar.editors.markdown.MarkdownEditorPane; |
| | +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.util.Action; |
| | +import com.scrivenvar.util.ActionUtils; |
| | +import static com.scrivenvar.util.StageState.*; |
| | +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*; |
| | +import java.net.MalformedURLException; |
| | +import java.nio.file.Path; |
| | +import java.util.HashMap; |
| | +import java.util.Map; |
| | +import java.util.Observable; |
| | +import java.util.Observer; |
| | +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.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 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 ); |
| | + |
| | + /** |
| | + * Prevent re-instantiation of classes for processing. |
| | + */ |
| | + private Map<FileEditorTab, Processor<String>> processors; |
| | + private ProcessorFactory processorFactory; |
| | + |
| | + private Scene scene; |
| | + private MenuBar menuBar; |
| | + |
| | + private DefinitionPane definitionPane; |
| | + private FileEditorTabPane fileEditorPane; |
| | + private HTMLPreviewPane previewPane; |
| | + |
| | + private DefinitionSource definitionSource; |
| | + |
| | + public MainWindow() { |
| | + initLayout(); |
| | + initOpenDefinitionListener(); |
| | + initTabAddedListener(); |
| | + initTabChangedListener(); |
| | + initPreferences(); |
| | + initWatchDog(); |
| | + } |
| | + |
| | + /** |
| | + * Listen for file editor tab pane to receive an open definition source event. |
| | + */ |
| | + private void initOpenDefinitionListener() { |
| | + getFileEditorPane().onOpenDefinitionFileProperty().addListener( |
| | + (ObservableValue<? extends Path> definitionFile, |
| | + final Path oldPath, final Path newPath) -> { |
| | + openDefinition( newPath ); |
| | + 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 ); |
| | + initVariableNameInjector( tab ); |
| | + } |
| | + } |
| | + } |
| | + } |
| | + ); |
| | + } |
| | + |
| | + /** |
| | + * Reloads the preferences from the previous load. |
| | + */ |
| | + private void initPreferences() { |
| | + getFileEditorPane().restorePreferences(); |
| | + restoreDefinitionSource(); |
| | + } |
| | + |
| | + /** |
| | + * 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) -> { |
| | + |
| | + // 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 ); |
| | + } |
| | + } |
| | + } |
| | + ); |
| | + } |
| | + |
| | + 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 initVariableNameInjector( final FileEditorTab tab ) { |
| | + VariableNameInjector.listen( tab, getDefinitionPane() ); |
| | + } |
| | + |
| | + private void initWatchDog() { |
| | + getSnitch().addObserver( this ); |
| | + } |
| | + |
| | + /** |
| | + * 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 ) { |
| | + getPreviewPane().setPath( tab.getPath() ); |
| | + |
| | + Processor<String> processor = getProcessors().get( tab ); |
| | + |
| | + if( processor == null ) { |
| | + processor = createProcessor( tab ); |
| | + getProcessors().put( tab, processor ); |
| | + } |
| | + |
| | + processor.processChain( tab.getEditorText() ); |
| | + } |
| | + |
| | + /** |
| | + * 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 ) { |
| | + alert( e ); |
| | + } |
| | + |
| | + return new TreeView<>(); |
| | + } |
| | + |
| | + private void openDefinition( final Path path ) { |
| | + openDefinition( path.toString() ); |
| | + } |
| | + |
| | + private void openDefinition( final String path ) { |
| | + try { |
| | + final DefinitionSource ds = createDefinitionSource( path ); |
| | + setDefinitionSource( ds ); |
| | + storeDefinitionSource(); |
| | + |
| | + getDefinitionPane().setRoot( ds.asTreeView() ); |
| | + } catch( Exception e ) { |
| | + alert( e ); |
| | + } |
| | + } |
| | + |
| | + private void restoreDefinitionSource() { |
| | + final Preferences preferences = getPreferences(); |
| | + final String source = preferences.get( PREFS_DEFINITION_SOURCE, null ); |
| | + |
| | + if( source != null ) { |
| | + openDefinition( source ); |
| | + } |
| | + } |
| | + |
| | + private void storeDefinitionSource() { |
| | + final Preferences preferences = getPreferences(); |
| | + final DefinitionSource ds = getDefinitionSource(); |
| | + |
| | + preferences.put( PREFS_DEFINITION_SOURCE, ds.toString() ); |
| | + } |
| | + |
| | + /** |
| | + * Called when the last open tab is closed. This clears out the preview pane |
| | + * and the definition pane. |
| | + */ |
| | + private void closeRemainingTab() { |
| | + getPreviewPane().clear(); |
| | + getDefinitionPane().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 alert( final Exception e ) { |
| | + // TODO: Update the status bar. |
| | + } |
| | + |
| | + //---- File actions ------------------------------------------------------- |
| | + /** |
| | + * Called when a file has been modified. |
| | + * |
| | + * @param snitch The watchdog file monitoring instance. |
| | + * @param file The file that was modified. |
| | + */ |
| | + @Override |
| | + public void update( final Observable snitch, final Object file ) { |
| | + if( file instanceof Path ) { |
| | + update( (Path)file ); |
| | + } |
| | + } |
| | + |
| | + /** |
| | + * Called when a file has been modified. |
| | + * |
| | + * @param file Path to the modified file. |
| | + */ |
| | + private void update( final Path file ) { |
| | + // Avoid throwing IllegalStateException by running from a non-JavaFX thread. |
| | + Platform.runLater( |
| | + () -> { |
| | + // Brute-force XSLT file reload by re-instantiating all processors. |
| | + getProcessors().clear(); |
| | + refreshSelectedTab( getActiveFileEditor() ); |
| | + } |
| | + ); |
| | + } |
| | + |
| | + //---- 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(); |
| | + fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) ); |
| | + } |
| | + |
| | + //---- Help actions ------------------------------------------------------- |
| | + private void helpAbout() { |
| | + Alert alert = new Alert( AlertType.INFORMATION ); |
| | + alert.setTitle( get( "Dialog.about.title" ) ); |
| | + alert.setHeaderText( get( "Dialog.about.header" ) ); |
| | + alert.setContentText( get( "Dialog.about.content" ) ); |
| | + alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) ); |
| | + alert.initOwner( getWindow() ); |
| | + |
| | + alert.showAndWait(); |
| | + } |
| | + |
| | + //---- Convenience accessors ---------------------------------------------- |
| | + private float getFloat( final String key, final float defaultValue ) { |
| | + return getPreferences().getFloat( key, defaultValue ); |
| | + } |
| | + |
| | + private Preferences getPreferences() { |
| | + return getOptions().getState(); |
| | + } |
| | + |
| | + private Window getWindow() { |
| | + return getScene().getWindow(); |
| | + } |
| | + |
| | + private MarkdownEditorPane getActiveEditor() { |
| | + final EditorPane pane = getActiveFileEditor().getEditorPane(); |
| | + |
| | + return pane instanceof MarkdownEditorPane ? (MarkdownEditorPane)pane : null; |
| | + } |
| | + |
| | + private FileEditorTab getActiveFileEditor() { |
| | + return getFileEditorPane().getActiveFileEditor(); |
| | + } |
| | + |
| | + //---- Member accessors --------------------------------------------------- |
| | + public Scene getScene() { |
| | + return this.scene; |
| | + } |
| | + |
| | + private void setScene( Scene scene ) { |
| | + this.scene = scene; |
| | + } |
| | + |
| | + private Map<FileEditorTab, Processor<String>> getProcessors() { |
| | + if( this.processors == null ) { |
| | + this.processors = new HashMap<>(); |
| | + } |
| | + |
| | + return this.processors; |
| | + } |
| | + |
| | + private ProcessorFactory getProcessorFactory() { |
| | + if( this.processorFactory == null ) { |
| | + this.processorFactory = createProcessorFactory(); |
| | + } |
| | + |
| | + return this.processorFactory; |
| | + } |
| | + |
| | + private FileEditorTabPane getFileEditorPane() { |
| | + if( this.fileEditorPane == null ) { |
| | + this.fileEditorPane = createFileEditorPane(); |
| | + } |
| | + |
| | + return this.fileEditorPane; |
| | + } |
| | + |
| | + private HTMLPreviewPane getPreviewPane() { |
| | + if( this.previewPane == null ) { |
| | + this.previewPane = createPreviewPane(); |
| | + } |
| | + |
| | + return this.previewPane; |
| | + } |
| | + |
| | + private void setDefinitionSource( final DefinitionSource definitionSource ) { |
| | + this.definitionSource = definitionSource; |
| | + } |
| | + |
| | + private DefinitionSource getDefinitionSource() { |
| | + if( this.definitionSource == null ) { |
| | + this.definitionSource = new EmptyDefinitionSource(); |
| | + } |
| | + |
| | + return this.definitionSource; |
| | + } |
| | + |
| | + private DefinitionPane getDefinitionPane() { |
| | + if( this.definitionPane == null ) { |
| | + this.definitionPane = createDefinitionPane(); |
| | + } |
| | + |
| | + return this.definitionPane; |
| | + } |
| | + |
| | + private Options getOptions() { |
| | + return this.options; |
| | + } |
| | + |
| | + private Snitch getSnitch() { |
| | + return this.snitch; |
| | + } |
| | + |
| | + public MenuBar getMenuBar() { |
| | + return this.menuBar; |
| | + } |
| | + |
| | + public void setMenuBar( MenuBar menuBar ) { |
| | + this.menuBar = menuBar; |
| | + } |
| | + |
| | + //---- Member creators ---------------------------------------------------- |
| | + /** |
| | + * Factory to create processors that are suited to different file types. |
| | + * |
| | + * @param tab The tab that is subjected to processing. |
| | + * |
| | + * @return A processor suited to the file type specified by the tab's path. |
| | + */ |
| | + private Processor<String> createProcessor( final FileEditorTab tab ) { |
| | + return getProcessorFactory().createProcessor( tab ); |
| | + } |
| | + |
| | + private ProcessorFactory createProcessorFactory() { |
| | + return new ProcessorFactory( getPreviewPane(), getResolvedMap() ); |
| | + } |
| | + |
| | private DefinitionSource createDefinitionSource( final String path ) |
| | throws MalformedURLException { |