Dave Jarvis' Repositories

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

Removed options. Started migration towards unified persistence.

Authordjarvis <email>
Date2016-12-10 14:52:02 GMT-0800
Commit64bbaed87a9b18eccd9512c85bd26548435ee651
Parenteaf59c3
.gitignore
/private
.nb-gradle-properties
-src/main/resources/com/scrivenvar/build.sh
+scrivenvar.pro
CREDITS.md
* Vladimir Schneider: [flexmark](https://website.com)
* Jens Deters: [FontAwesomeFX](https://bitbucket.org/Jerady/fontawesomefx)
+ * Apache Tika Team: [Apache Tika](https://tika.apache.org/)
README.md
* User-defined variables
-* Preview variable values in real time
+* Recursive variable definitions
+* Real-time document preview with variable substitution
* Platform independent (Windows, Linux, MacOS)
-* Auto-insert variable names by typing in values followed by `Control+Space`
+* Auto-insert variable names pressing `Control+Space`
Future Features
---
* Spell check
+* Search and replace, with or without variables
* XML and XSL processing
-* Search and replace text with variables
* R integration using [Rserve](https://rforge.net/Rserve/)
+* Re-organize variable names
Requirements
build.gradle
compile group: 'com.miglayout', name: 'miglayout-javafx', version: '5.0'
compile group: 'de.jensd', name: 'fontawesomefx-fontawesome', version: '4.5.0'
- compile group: 'commons-configuration', name: 'commons-configuration', version: '1.10'
+ compile group: 'org.ahocorasick', name: 'ahocorasick', version: '0.3.0'
compile group: 'com.vladsch.flexmark', name: 'flexmark', version: '0.6.1'
compile group: 'com.vladsch.flexmark', name: 'flexmark-ext-gfm-tables', version: '0.6.1'
- compile group: 'org.yaml', name: 'snakeyaml', version: '1.17'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.4'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.4'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.8.4'
compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.8.4'
- compile group: 'org.ahocorasick', name: 'ahocorasick', version: '0.3.0'
- compile group: 'junit', name: 'junit', version: '4.4'
+ compile group: 'org.yaml', name: 'snakeyaml', version: '1.17'
+ compile group: 'com.googlecode.juniversalchardet', name: 'juniversalchardet', version: '1.0.3'
+ compile group: 'commons-configuration', name: 'commons-configuration', version: '1.10'
}
jar {
baseName = 'scrivenvar'
+
+ from {
+ (configurations.runtime).collect {
+ it.isDirectory() ? it : zipTree(it)
+ }
+ }
+
manifest {
attributes 'Main-Class': mainClassName,
images/screenshot.png
Binary files differ
src/main/java/com/scrivenvar/FileEditorTab.java
import com.scrivenvar.service.events.AlertMessage;
import com.scrivenvar.service.events.AlertService;
-import java.io.IOException;
+import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
+import static java.util.Locale.ENGLISH;
import java.util.function.Consumer;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.event.Event;
-import javafx.scene.control.Alert;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
import org.fxmisc.wellbehaved.event.EventPattern;
import org.fxmisc.wellbehaved.event.InputMap;
+import org.mozilla.universalchardet.UniversalDetector;
/**
private EditorPane editorPane;
private HTMLPreviewPane previewPane;
+
+ /**
+ * Character encoding used by the file (or default encoding if none found).
+ */
+ private Charset encoding;
private final ReadOnlyBooleanWrapper modified = new ReadOnlyBooleanWrapper();
private void updateTab() {
- final Path filePath = getPath();
-
- setText( getFilename( filePath ) );
+ setText( getTabTitle() );
setGraphic( getModifiedMark() );
- setTooltip( getTooltip( filePath ) );
+ setTooltip( getTabTooltip() );
}
- private String getFilename( final Path filePath ) {
+ /**
+ * Returns the base filename (without the directory names).
+ *
+ * @return The untitled text if the path hasn't been set.
+ */
+ private String getTabTitle() {
+ final Path filePath = getPath();
+
return (filePath == null)
? Messages.get( "FileEditor.untitled" )
: filePath.getFileName().toString();
}
- private Tooltip getTooltip( final Path filePath ) {
+ /**
+ * Returns the full filename represented by the path.
+ *
+ * @return The untitled text if the path hasn't been set.
+ */
+ private Tooltip getTabTooltip() {
+ final Path filePath = getPath();
+
return (filePath == null)
? null
: new Tooltip( filePath.toString() );
}
+ /**
+ * Returns a marker to indicate whether the file has been modified.
+ *
+ * @return "*" when the file has changed; otherwise null.
+ */
private Text getModifiedMark() {
return isModified() ? new Text( "*" ) : null;
}
/**
* Called when the user switches tab.
*/
private void activated() {
+ // Tab is closed or no longer active.
if( getTabPane() == null || !isSelected() ) {
- // Tab is closed or no longer active.
return;
}
+ // Switch to the tab without loading if the contents are already in memory.
if( getContent() != null ) {
getEditorPane().requestFocus();
return;
}
// Load the text and update the preview before the undo manager.
load();
- // Track undo requests (must not be called before load).
+ // Track undo requests (*must* be called after load).
initUndoManager();
initSplitPane();
+ initFocus();
}
public void initSplitPane() {
final EditorPane editor = getEditorPane();
final HTMLPreviewPane preview = getPreviewPane();
-
final VirtualizedScrollPane<StyleClassedTextArea> editorScrollPane = editor.getScrollPane();
// Make the preview pane scroll correspond to the editor pane scroll.
// Separate the edit and preview panels.
- final SplitPane splitPane = new SplitPane(
- editorScrollPane,
- preview.getWebView() );
- setContent( splitPane );
+ setContent( new SplitPane( editorScrollPane, preview.getNode() ) );
+ }
- // Let the user edit.
- editor.requestFocus();
+ private void initFocus() {
+ getEditorPane().requestFocus();
}
}
- void load() {
+ /**
+ * Returns true if the given path exactly matches this tab's path.
+ *
+ * @param check The path to compare against.
+ *
+ * @return true The paths are the same.
+ */
+ public boolean isPath( final Path check ) {
final Path filePath = getPath();
- if( filePath != null ) {
- try {
- final byte[] bytes = Files.readAllBytes( filePath );
- String markdown;
+ return filePath == null ? false : filePath.equals( check );
+ }
- try {
- markdown = new String( bytes, getOptions().getEncoding() );
- } catch( Exception e ) {
- // Unsupported encodings and null pointers fallback here.
- markdown = new String( bytes );
- }
+ /**
+ * Reads the entire file contents from the path associated with this tab.
+ */
+ private void load() {
+ final Path filePath = getPath();
- getEditorPane().setText( markdown );
- } catch( IOException ex ) {
- final AlertMessage message = getAlertService().createAlertMessage(
- Messages.get( "FileEditor.loadFailed.title" ),
- Messages.get( "FileEditor.loadFailed.message" ),
- filePath,
- ex.getMessage()
+ if( filePath != null ) {
+ try {
+ getEditorPane().setText( asString( Files.readAllBytes( filePath ) ) );
+ } catch( Exception ex ) {
+ alert(
+ "FileEditor.loadFailed.title", "FileEditor.loadFailed.message", ex
);
-
- final Alert alert = getAlertService().createAlertError( message );
-
- alert.showAndWait();
}
}
}
-
- boolean save() {
- final String text = getEditorPane().getText();
-
- byte[] bytes;
-
- try {
- bytes = text.getBytes( getOptions().getEncoding() );
- } catch( Exception ex ) {
- bytes = text.getBytes();
- }
+ /**
+ * Saves the entire file contents from the path associated with this tab.
+ *
+ * @return true The file has been saved.
+ */
+ public boolean save() {
try {
- Files.write( getPath(), bytes );
+ Files.write( getPath(), asBytes( getEditorPane().getText() ) );
getEditorPane().getUndoManager().mark();
return true;
- } catch( IOException ex ) {
- final AlertService service = getAlertService();
- final AlertMessage message = service.createAlertMessage(
- Messages.get( "FileEditor.saveFailed.title" ),
- Messages.get( "FileEditor.saveFailed.message" ),
- getPath(),
- ex.getMessage()
+ } catch( Exception ex ) {
+ return alert(
+ "FileEditor.saveFailed.title", "FileEditor.saveFailed.message", ex
);
+ }
+ }
- final Alert alert = service.createAlertError( message );
+ /**
+ * Creates an alert dialog and waits for it to close.
+ *
+ * @param titleKey Resource bundle key for the alert dialog title.
+ * @param messageKey Resource bundle key for the alert dialog message.
+ * @param e The unexpected happening.
+ *
+ * @return false
+ */
+ private boolean alert( String titleKey, String messageKey, Exception e ) {
+ final AlertService service = getAlertService();
- alert.showAndWait();
- return false;
- }
+ final AlertMessage message = service.createAlertMessage(
+ Messages.get( titleKey ),
+ Messages.get( messageKey ),
+ getPath(),
+ e.getMessage()
+ );
+
+ service.createAlertError( message ).showAndWait();
+ return false;
+ }
+
+ /**
+ * Returns a best guess at the file encoding. If the encoding could not be
+ * detected, this will return the default charset for the JVM.
+ *
+ * @param bytes The bytes to perform character encoding detection.
+ *
+ * @return The character encoding.
+ */
+ private Charset detectEncoding( final byte[] bytes ) {
+ final UniversalDetector detector = new UniversalDetector( null );
+ detector.handleData( bytes, 0, bytes.length );
+ detector.dataEnd();
+
+ final String charset = detector.getDetectedCharset();
+ final Charset charEncoding = charset == null
+ ? Charset.defaultCharset()
+ : Charset.forName( charset.toUpperCase( ENGLISH ) );
+
+ detector.reset();
+
+ return charEncoding;
+ }
+
+ /**
+ * Converts the given string to an array of bytes using the encoding that was
+ * originally detected (if any) and associated with this file.
+ *
+ * @param text The text to convert into the original file encoding.
+ *
+ * @return A series of bytes ready for writing to a file.
+ */
+ private byte[] asBytes( final String text ) {
+ return text.getBytes( getEncoding() );
+ }
+
+ /**
+ * Converts the given bytes into a Java String. This will call setEncoding
+ * with the encoding detected by the CharsetDetector.
+ *
+ * @param text The text of unknown character encoding.
+ *
+ * @return The text, in its auto-detected encoding, as a String.
+ */
+ private String asString( final byte[] text ) {
+ setEncoding( detectEncoding( text ) );
+ return new String( text, getEncoding() );
}
}
- boolean isModified() {
+ public boolean isModified() {
return this.modified.get();
}
}
+ /**
+ * Forwards the request to the editor pane.
+ *
+ * @param <T> The type of event listener to add.
+ * @param <U> The type of consumer to add.
+ * @param event The event that should trigger updates to the listener.
+ * @param consumer The listener to receive update events.
+ */
public <T extends Event, U extends T> void addEventListener(
final EventPattern<? super T, ? extends U> event,
final Consumer<? super U> consumer ) {
getEditorPane().addEventListener( event, consumer );
}
+ /**
+ * Forwards to the editor pane's listeners for keyboard events.
+ *
+ * @param map The new input map to replace the existing keyboard listener.
+ */
public void addEventListener( final InputMap<InputEvent> map ) {
getEditorPane().addEventListener( map );
}
+ /**
+ * Forwards to the editor pane's listeners for keyboard events.
+ *
+ * @param map The existing input map to remove from the keyboard listeners.
+ */
public void removeEventListener( final InputMap<InputEvent> map ) {
getEditorPane().removeEventListener( map );
}
+ /**
+ * Returns the editor pane, or creates one if it doesn't yet exist.
+ *
+ * @return The editor pane, never null.
+ */
protected EditorPane getEditorPane() {
if( this.editorPane == null ) {
return this.previewPane;
+ }
+
+ private Charset getEncoding() {
+ return this.encoding;
+ }
+
+ private void setEncoding( final Charset encoding ) {
+ this.encoding = encoding;
}
}
src/main/java/com/scrivenvar/FileEditorTabPane.java
private List<FileEditorTab> openEditors( final List<File> files, final int activeIndex ) {
- final List<FileEditorTab> editors = new ArrayList<>();
+ final int fileTally = files.size();
+ final List<FileEditorTab> editors = new ArrayList<>( fileTally );
final List<Tab> tabs = getTabs();
-
+
// Close single unmodified "Untitled" tab.
if( tabs.size() == 1 ) {
final FileEditorTab fileEditor = (FileEditorTab)(tabs.get( 0 ).getUserData());
if( fileEditor.getPath() == null && !fileEditor.isModified() ) {
closeEditor( fileEditor, false );
}
}
- for( int i = 0; i < files.size(); i++ ) {
- Path path = files.get( i ).toPath();
+ for( int i = 0; i < fileTally; i++ ) {
+ final Path path = files.get( i ).toPath();
// Check whether file is already opened.
boolean saveAllEditors() {
- final FileEditorTab[] allEditors = getAllEditors();
-
boolean success = true;
- for( FileEditorTab fileEditor : allEditors ) {
+
+ for( FileEditorTab fileEditor : getAllEditors() ) {
if( !saveEditor( fileEditor ) ) {
success = false;
Event event = new Event( tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT );
Event.fireEvent( tab, event );
+
if( event.isConsumed() ) {
return false;
}
}
getTabs().remove( tab );
+
if( tab.getOnClosed() != null ) {
Event.fireEvent( tab, new Event( Tab.CLOSED_EVENT ) );
}
return true;
}
boolean closeAllEditors() {
- FileEditorTab[] allEditors = getAllEditors();
- FileEditorTab activeEditor = activeFileEditor.get();
+ final FileEditorTab[] allEditors = getAllEditors();
+ final FileEditorTab activeEditor = getActiveFileEditor();
// try to save active tab first because in case the user decides to cancel,
// then it stays active
if( activeEditor != null && !canCloseEditor( activeEditor ) ) {
return false;
}
+
+ // This should be called any time a tab changes.
+ persistPreferences();
// save modified tabs
for( int i = 0; i < allEditors.length; i++ ) {
- FileEditorTab fileEditor = allEditors[ i ];
+ final FileEditorTab fileEditor = allEditors[ i ];
+
if( fileEditor == activeEditor ) {
continue;
}
}
-
- saveState( allEditors, activeEditor );
return getTabs().isEmpty();
}
- private FileEditorTab findEditor( Path path ) {
+ /**
+ * Returns the file editor tab that has the given path.
+ *
+ * @return null No file editor tab for the given path was found.
+ */
+ private FileEditorTab findEditor( final Path path ) {
for( final Tab tab : getTabs() ) {
- final FileEditorTab fileEditor = (FileEditorTab)tab.getUserData();
+ final FileEditorTab fileEditor = (FileEditorTab)tab;
- if( path.equals( fileEditor.getPath() ) ) {
+ System.out.println( "path = " + path );
+ System.out.println( "fileEditor = " + fileEditor.isPath( path ) );
+
+ if( fileEditor.isPath( path ) ) {
return fileEditor;
}
}
return null;
- }
-
- private Settings getSettings() {
- return this.settings;
}
}
- private void saveLastDirectory( File file ) {
+ private void saveLastDirectory( final File file ) {
getState().put( "lastDirectory", file.getParent() );
}
- public void restoreState() {
+ public void restorePreferences() {
int activeIndex = 0;
- final Preferences state = getState();
- final String[] fileNames = Utils.getPrefsStrings( state, "file" );
- final String activeFileName = state.get( "activeFile", null );
+ final Preferences preferences = getState();
+ final String[] fileNames = Utils.getPrefsStrings( preferences, "file" );
+ final String activeFileName = preferences.get( "activeFile", null );
final ArrayList<File> files = new ArrayList<>( fileNames.length );
}
- private void saveState( final FileEditorTab[] allEditors, final FileEditorTab activeEditor ) {
- final List<String> fileNames = new ArrayList<>( allEditors.length );
+ public void persistPreferences() {
+ final ObservableList<Tab> allEditors = getTabs();
+ final List<String> fileNames = new ArrayList<>( allEditors.size() );
- for( final FileEditorTab fileEditor : allEditors ) {
+ for( final Tab tab : allEditors ) {
+ final FileEditorTab fileEditor = (FileEditorTab)tab;
+
if( fileEditor.getPath() != null ) {
fileNames.add( fileEditor.getPath().toString() );
}
}
- final Preferences state = getState();
- Utils.putPrefsStrings( state, "file", fileNames.toArray( new String[ fileNames.size() ] ) );
+ final Preferences preferences = getState();
+ Utils.putPrefsStrings( preferences, "file", fileNames.toArray( new String[ fileNames.size() ] ) );
+
+ final FileEditorTab activeEditor = getActiveFileEditor();
if( activeEditor != null && activeEditor.getPath() != null ) {
- state.put( "activeFile", activeEditor.getPath().toString() );
+ preferences.put( "activeFile", activeEditor.getPath().toString() );
} else {
- state.remove( "activeFile" );
+ preferences.remove( "activeFile" );
}
}
- private Window getWindow() {
- return getScene().getWindow();
+ private Settings getSettings() {
+ return this.settings;
}
protected Options getOptions() {
return this.options;
+ }
+
+ private Window getWindow() {
+ return getScene().getWindow();
}
protected Preferences getState() {
return getOptions().getState();
}
-
}
src/main/java/com/scrivenvar/MainWindow.java
import com.scrivenvar.editor.MarkdownEditorPane;
import com.scrivenvar.editor.VariableNameInjector;
-import com.scrivenvar.options.OptionsDialog;
-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 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 FileEditorTabPane fileEditorPane;
- private DefinitionPane definitionPane;
-
- private VariableNameInjector variableNameInjector;
-
- private YamlTreeAdapter yamlTreeAdapter;
- private YamlParser yamlParser;
-
- private MenuBar menuBar;
-
- public MainWindow() {
- initLayout();
- initVariableNameInjector();
- }
-
- private void initLayout() {
- final SplitPane splitPane = new SplitPane(
- getDefinitionPane().getNode(),
- getFileEditorPane().getNode() );
-
- splitPane.setDividerPositions(
- getFloat( K_PANE_SPLIT_DEFINITION, .05f ),
- getFloat( K_PANE_SPLIT_EDITOR, .95f ) );
-
- // See: http://broadlyapplicable.blogspot.ca/2015/03/javafx-capture-restore-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 ) );
- }
-
- //---- Tools actions ------------------------------------------------------
- private void toolsOptions() {
- new OptionsDialog( getWindow() ).showAndWait();
- }
-
- //---- 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;
- }
-
- private FileEditorTabPane createFileEditorPane() {
- // Create an editor pane to hold file editor tabs.
- final FileEditorTabPane editorPane = new FileEditorTabPane();
-
- // Make sure the text processor kicks off when new files are opened.
- final ObservableList<Tab> tabs = editorPane.getTabs();
-
- tabs.addListener( (Change<? extends Tab> change) -> {
- while( change.next() ) {
- if( change.wasAdded() ) {
- // Multiple tabs can be added simultaneously.
- for( final Tab tab : change.getAddedSubList() ) {
- addListener( (FileEditorTab)tab );
- }
- }
- }
- } );
-
- // After the processors are in place, restore the previously closed
- // tabs. Adding them will trigger the change event, above.
- editorPane.restoreState();
-
- return editorPane;
- }
-
- private MarkdownEditorPane getActiveEditor() {
- return (MarkdownEditorPane)(getActiveFileEditor().getEditorPane());
- }
-
- private FileEditorTab getActiveFileEditor() {
- return getFileEditorPane().getActiveFileEditor();
- }
-
- /**
- * 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( FileEditorTab tab ) {
- final HTMLPreviewPane previewPane = tab.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( previewPane );
- final Processor<String> mcrp = new MarkdownCaretReplacementProcessor( hpp );
- final Processor<String> mp = new MarkdownProcessor( mcrp );
- final Processor<String> mcip = new MarkdownCaretInsertionProcessor( mp, editor );
- final Processor<String> vnp = new VariableProcessor( mcip, getResolvedMap() );
- final TextChangeProcessor tp = new TextChangeProcessor( vnp );
-
- editorPanel.addChangeListener( tp );
- 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;
- }
-
- public void setMenuBar( MenuBar menuBar ) {
- this.menuBar = menuBar;
- }
-
- public VariableNameInjector getVariableNameInjector() {
- return this.variableNameInjector;
- }
-
- public void setVariableNameInjector( VariableNameInjector variableNameInjector ) {
- this.variableNameInjector = variableNameInjector;
- }
-
- private float getFloat( final String key, final float defaultValue ) {
- return getPreferences().getFloat( key, defaultValue );
- }
-
- private Preferences getPreferences() {
- return getOptions().getState();
- }
-
- private Options getOptions() {
- return this.options;
- }
-
- private synchronized TreeView<String> getTreeView() throws RuntimeException {
- if( this.treeView == null ) {
- try {
- this.treeView = createTreeView();
- } catch( IOException ex ) {
-
- // TODO: Pop an error message.
- throw new RuntimeException( ex );
- }
- }
-
- return this.treeView;
- }
-
- private InputStream asStream( final String resource ) {
- return getClass().getResourceAsStream( resource );
- }
-
- private TreeView<String> createTreeView() throws IOException {
- // TODO: Associate variable file with path to current file.
- return getYamlTreeAdapter().adapt(
- asStream( "/com/scrivenvar/variables.yaml" ),
- get( "Pane.defintion.node.root.title" )
- );
- }
-
- private Map<String, String> getResolvedMap() {
- return getYamlParser().createResolvedMap();
- }
-
- private YamlTreeAdapter getYamlTreeAdapter() {
- if( this.yamlTreeAdapter == null ) {
- setYamlTreeAdapter( new YamlTreeAdapter( getYamlParser() ) );
- }
-
- return this.yamlTreeAdapter;
- }
-
- private void setYamlTreeAdapter( final YamlTreeAdapter yamlTreeAdapter ) {
- this.yamlTreeAdapter = yamlTreeAdapter;
- }
-
- private YamlParser getYamlParser() {
- if( this.yamlParser == null ) {
- setYamlParser( new YamlParser() );
- }
-
- return this.yamlParser;
- }
-
- private void setYamlParser( final YamlParser yamlParser ) {
- this.yamlParser = yamlParser;
- }
-
- private Node createMenuBar() {
- final BooleanBinding activeFileEditorIsNull = getFileEditorPane().activeFileEditorProperty().isNull();
-
- // File actions
- Action fileNewAction = new Action( Messages.get( "Main.menu.file.new" ), "Shortcut+N", FILE_ALT, e -> fileNew() );
- Action fileOpenAction = new Action( Messages.get( "Main.menu.file.open" ), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen() );
- Action fileCloseAction = new Action( Messages.get( "Main.menu.file.close" ), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull );
- Action fileCloseAllAction = new Action( Messages.get( "Main.menu.file.close_all" ), null, null, e -> fileCloseAll(), activeFileEditorIsNull );
- Action fileSaveAction = new Action( Messages.get( "Main.menu.file.save" ), "Shortcut+S", FLOPPY_ALT, e -> fileSave(),
- createActiveBooleanProperty( FileEditorTab::modifiedProperty ).not() );
- Action fileSaveAllAction = new Action( Messages.get( "Main.menu.file.save_all" ), "Shortcut+Shift+S", null, e -> fileSaveAll(),
- Bindings.not( getFileEditorPane().anyFileEditorModifiedProperty() ) );
- Action fileExitAction = new Action( Messages.get( "Main.menu.file.exit" ), null, null, e -> fileExit() );
-
- // Edit actions
- Action editUndoAction = new Action( Messages.get( "Main.menu.edit.undo" ), "Shortcut+Z", UNDO,
- e -> getActiveEditor().undo(),
- createActiveBooleanProperty( FileEditorTab::canUndoProperty ).not() );
- Action editRedoAction = new Action( Messages.get( "Main.menu.edit.redo" ), "Shortcut+Y", REPEAT,
- e -> getActiveEditor().redo(),
- createActiveBooleanProperty( FileEditorTab::canRedoProperty ).not() );
-
- // Insert actions
- Action insertBoldAction = new Action( Messages.get( "Main.menu.insert.bold" ), "Shortcut+B", BOLD,
- e -> getActiveEditor().surroundSelection( "**", "**" ),
- activeFileEditorIsNull );
- Action insertItalicAction = new Action( Messages.get( "Main.menu.insert.italic" ), "Shortcut+I", ITALIC,
- e -> getActiveEditor().surroundSelection( "*", "*" ),
- activeFileEditorIsNull );
- Action insertStrikethroughAction = new Action( Messages.get( "Main.menu.insert.strikethrough" ), "Shortcut+T", STRIKETHROUGH,
- e -> getActiveEditor().surroundSelection( "~~", "~~" ),
- activeFileEditorIsNull );
- Action insertBlockquoteAction = new Action( Messages.get( "Main.menu.insert.blockquote" ), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac
- e -> getActiveEditor().surroundSelection( "\n\n> ", "" ),
- activeFileEditorIsNull );
- Action insertCodeAction = new Action( Messages.get( "Main.menu.insert.code" ), "Shortcut+K", CODE,
- e -> getActiveEditor().surroundSelection( "`", "`" ),
- activeFileEditorIsNull );
- Action insertFencedCodeBlockAction = new Action( Messages.get( "Main.menu.insert.fenced_code_block" ), "Shortcut+Shift+K", FILE_CODE_ALT,
- e -> getActiveEditor().surroundSelection( "\n\n```\n", "\n```\n\n", Messages.get( "Main.menu.insert.fenced_code_block.prompt" ) ),
- activeFileEditorIsNull );
-
- Action insertLinkAction = new Action( Messages.get( "Main.menu.insert.link" ), "Shortcut+L", LINK,
- e -> getActiveEditor().insertLink(),
- activeFileEditorIsNull );
- Action insertImageAction = new Action( Messages.get( "Main.menu.insert.image" ), "Shortcut+G", PICTURE_ALT,
- e -> getActiveEditor().insertImage(),
- activeFileEditorIsNull );
-
- final Action[] headers = new Action[ 6 ];
-
- // Insert header actions (H1 ... H6)
- for( int i = 1; i <= 6; i++ ) {
- final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
- final String markup = String.format( "\n\n%s ", hashes );
- final String text = Messages.get( "Main.menu.insert.header_" + i );
- final String accelerator = "Shortcut+" + i;
- final String prompt = Messages.get( "Main.menu.insert.header_" + i + ".prompt" );
-
- headers[ i - 1 ] = new Action( text, accelerator, HEADER,
- e -> getActiveEditor().surroundSelection( markup, "", prompt ),
- activeFileEditorIsNull );
- }
-
- Action insertUnorderedListAction = new Action( Messages.get( "Main.menu.insert.unordered_list" ), "Shortcut+U", LIST_UL,
- e -> getActiveEditor().surroundSelection( "\n\n* ", "" ),
- activeFileEditorIsNull );
- Action insertOrderedListAction = new Action( Messages.get( "Main.menu.insert.ordered_list" ), "Shortcut+Shift+O", LIST_OL,
- e -> getActiveEditor().surroundSelection( "\n\n1. ", "" ),
- activeFileEditorIsNull );
- Action insertHorizontalRuleAction = new Action( Messages.get( "Main.menu.insert.horizontal_rule" ), "Shortcut+H", null,
- e -> getActiveEditor().surroundSelection( "\n\n---\n\n", "" ),
- activeFileEditorIsNull );
-
- // Tools actions
- Action toolsOptionsAction = new Action( Messages.get( "Main.menu.tools.options" ), "Shortcut+,", null, e -> toolsOptions() );
-
- // Help actions
- Action helpAboutAction = new Action( Messages.get( "Main.menu.help.about" ), null, null, e -> helpAbout() );
-
- //---- MenuBar ----
- Menu fileMenu = ActionUtils.createMenu( Messages.get( "Main.menu.file" ),
- fileNewAction,
- fileOpenAction,
- null,
- fileCloseAction,
- fileCloseAllAction,
- null,
- fileSaveAction,
- fileSaveAllAction,
- null,
- fileExitAction );
-
- Menu editMenu = ActionUtils.createMenu( Messages.get( "Main.menu.edit" ),
- editUndoAction,
- editRedoAction );
-
- Menu insertMenu = ActionUtils.createMenu( Messages.get( "Main.menu.insert" ),
- insertBoldAction,
- insertItalicAction,
- insertStrikethroughAction,
- insertBlockquoteAction,
- insertCodeAction,
- insertFencedCodeBlockAction,
- null,
- insertLinkAction,
- insertImageAction,
- null,
- headers[ 0 ],
- headers[ 1 ],
- headers[ 2 ],
- headers[ 3 ],
- headers[ 4 ],
- headers[ 5 ],
- null,
- insertUnorderedListAction,
- insertOrderedListAction,
- insertHorizontalRuleAction );
-
- Menu toolsMenu = ActionUtils.createMenu( Messages.get( "Main.menu.tools" ),
- toolsOptionsAction );
-
- Menu helpMenu = ActionUtils.createMenu( Messages.get( "Main.menu.help" ),
- helpAboutAction );
-
- menuBar = new MenuBar( fileMenu, editMenu, insertMenu, toolsMenu, helpMenu );
+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 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;
+import static com.scrivenvar.Messages.get;
+import static com.scrivenvar.Messages.get;
+import static com.scrivenvar.Messages.get;
+import static com.scrivenvar.Messages.get;
+import static com.scrivenvar.Messages.get;
+import static com.scrivenvar.Messages.get;
+import static com.scrivenvar.Messages.get;
+
+/**
+ * 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 FileEditorTabPane fileEditorPane;
+ private DefinitionPane definitionPane;
+
+ private VariableNameInjector variableNameInjector;
+
+ private YamlTreeAdapter yamlTreeAdapter;
+ private YamlParser yamlParser;
+
+ private MenuBar menuBar;
+
+ public MainWindow() {
+ initLayout();
+ initVariableNameInjector();
+ }
+
+ private void initLayout() {
+ final SplitPane splitPane = new SplitPane(
+ getDefinitionPane().getNode(),
+ getFileEditorPane().getNode() );
+
+ splitPane.setDividerPositions(
+ getFloat( K_PANE_SPLIT_DEFINITION, .05f ),
+ getFloat( K_PANE_SPLIT_EDITOR, .95f ) );
+
+ // 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;
+ }
+
+ private FileEditorTabPane createFileEditorPane() {
+ // Create an editor pane to hold file editor tabs.
+ final FileEditorTabPane editorPane = new FileEditorTabPane();
+
+ // Make sure the text processor kicks off when new files are opened.
+ final ObservableList<Tab> tabs = editorPane.getTabs();
+
+ tabs.addListener( (Change<? extends Tab> change) -> {
+ while( change.next() ) {
+ if( change.wasAdded() ) {
+ // Multiple tabs can be added simultaneously.
+ for( final Tab tab : change.getAddedSubList() ) {
+ addListener( (FileEditorTab)tab );
+ }
+ }
+ }
+ } );
+
+ // After the processors are in place, restorePreferences the previously closed
+ // tabs. Adding them will trigger the change event, above.
+ editorPane.restorePreferences();
+
+ return editorPane;
+ }
+
+ private MarkdownEditorPane getActiveEditor() {
+ return (MarkdownEditorPane)(getActiveFileEditor().getEditorPane());
+ }
+
+ private FileEditorTab getActiveFileEditor() {
+ return getFileEditorPane().getActiveFileEditor();
+ }
+
+ /**
+ * 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( FileEditorTab tab ) {
+ final HTMLPreviewPane previewPane = tab.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( previewPane );
+ final Processor<String> mcrp = new MarkdownCaretReplacementProcessor( hpp );
+ final Processor<String> mp = new MarkdownProcessor( mcrp );
+ final Processor<String> mcip = new MarkdownCaretInsertionProcessor( mp, editor );
+ final Processor<String> vnp = new VariableProcessor( mcip, getResolvedMap() );
+ final TextChangeProcessor tp = new TextChangeProcessor( vnp );
+
+ editorPanel.addChangeListener( tp );
+ 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;
+ }
+
+ public void setMenuBar( MenuBar menuBar ) {
+ this.menuBar = menuBar;
+ }
+
+ public VariableNameInjector getVariableNameInjector() {
+ return this.variableNameInjector;
+ }
+
+ public void setVariableNameInjector( VariableNameInjector variableNameInjector ) {
+ this.variableNameInjector = variableNameInjector;
+ }
+
+ private float getFloat( final String key, final float defaultValue ) {
+ return getPreferences().getFloat( key, defaultValue );
+ }
+
+ private Preferences getPreferences() {
+ return getOptions().getState();
+ }
+
+ private Options getOptions() {
+ return this.options;
+ }
+
+ private synchronized TreeView<String> getTreeView() throws RuntimeException {
+ if( this.treeView == null ) {
+ try {
+ this.treeView = createTreeView();
+ } catch( IOException ex ) {
+
+ // TODO: Pop an error message.
+ throw new RuntimeException( ex );
+ }
+ }
+
+ return this.treeView;
+ }
+
+ private InputStream asStream( final String resource ) {
+ return getClass().getResourceAsStream( resource );
+ }
+
+ private TreeView<String> createTreeView() throws IOException {
+ // TODO: Associate variable file with path to current file.
+ return getYamlTreeAdapter().adapt(
+ asStream( "/com/scrivenvar/variables.yaml" ),
+ get( "Pane.defintion.node.root.title" )
+ );
+ }
+
+ private Map<String, String> getResolvedMap() {
+ return getYamlParser().createResolvedMap();
+ }
+
+ private YamlTreeAdapter getYamlTreeAdapter() {
+ if( this.yamlTreeAdapter == null ) {
+ setYamlTreeAdapter( new YamlTreeAdapter( getYamlParser() ) );
+ }
+
+ return this.yamlTreeAdapter;
+ }
+
+ private void setYamlTreeAdapter( final YamlTreeAdapter yamlTreeAdapter ) {
+ this.yamlTreeAdapter = yamlTreeAdapter;
+ }
+
+ private YamlParser getYamlParser() {
+ if( this.yamlParser == null ) {
+ setYamlParser( new YamlParser() );
+ }
+
+ return this.yamlParser;
+ }
+
+ private void setYamlParser( final YamlParser yamlParser ) {
+ this.yamlParser = yamlParser;
+ }
+
+ private Node createMenuBar() {
+ final BooleanBinding activeFileEditorIsNull = getFileEditorPane().activeFileEditorProperty().isNull();
+
+ // File actions
+ Action fileNewAction = new Action( Messages.get( "Main.menu.file.new" ), "Shortcut+N", FILE_ALT, e -> fileNew() );
+ Action fileOpenAction = new Action( Messages.get( "Main.menu.file.open" ), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen() );
+ Action fileCloseAction = new Action( Messages.get( "Main.menu.file.close" ), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull );
+ Action fileCloseAllAction = new Action( Messages.get( "Main.menu.file.close_all" ), null, null, e -> fileCloseAll(), activeFileEditorIsNull );
+ Action fileSaveAction = new Action( Messages.get( "Main.menu.file.save" ), "Shortcut+S", FLOPPY_ALT, e -> fileSave(),
+ createActiveBooleanProperty( FileEditorTab::modifiedProperty ).not() );
+ Action fileSaveAllAction = new Action( Messages.get( "Main.menu.file.save_all" ), "Shortcut+Shift+S", null, e -> fileSaveAll(),
+ Bindings.not( getFileEditorPane().anyFileEditorModifiedProperty() ) );
+ Action fileExitAction = new Action( Messages.get( "Main.menu.file.exit" ), null, null, e -> fileExit() );
+
+ // Edit actions
+ Action editUndoAction = new Action( Messages.get( "Main.menu.edit.undo" ), "Shortcut+Z", UNDO,
+ e -> getActiveEditor().undo(),
+ createActiveBooleanProperty( FileEditorTab::canUndoProperty ).not() );
+ Action editRedoAction = new Action( Messages.get( "Main.menu.edit.redo" ), "Shortcut+Y", REPEAT,
+ e -> getActiveEditor().redo(),
+ createActiveBooleanProperty( FileEditorTab::canRedoProperty ).not() );
+
+ // Insert actions
+ Action insertBoldAction = new Action( Messages.get( "Main.menu.insert.bold" ), "Shortcut+B", BOLD,
+ e -> getActiveEditor().surroundSelection( "**", "**" ),
+ activeFileEditorIsNull );
+ Action insertItalicAction = new Action( Messages.get( "Main.menu.insert.italic" ), "Shortcut+I", ITALIC,
+ e -> getActiveEditor().surroundSelection( "*", "*" ),
+ activeFileEditorIsNull );
+ Action insertStrikethroughAction = new Action( Messages.get( "Main.menu.insert.strikethrough" ), "Shortcut+T", STRIKETHROUGH,
+ e -> getActiveEditor().surroundSelection( "~~", "~~" ),
+ activeFileEditorIsNull );
+ Action insertBlockquoteAction = new Action( Messages.get( "Main.menu.insert.blockquote" ), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac
+ e -> getActiveEditor().surroundSelection( "\n\n> ", "" ),
+ activeFileEditorIsNull );
+ Action insertCodeAction = new Action( Messages.get( "Main.menu.insert.code" ), "Shortcut+K", CODE,
+ e -> getActiveEditor().surroundSelection( "`", "`" ),
+ activeFileEditorIsNull );
+ Action insertFencedCodeBlockAction = new Action( Messages.get( "Main.menu.insert.fenced_code_block" ), "Shortcut+Shift+K", FILE_CODE_ALT,
+ e -> getActiveEditor().surroundSelection( "\n\n```\n", "\n```\n\n", Messages.get( "Main.menu.insert.fenced_code_block.prompt" ) ),
+ activeFileEditorIsNull );
+
+ Action insertLinkAction = new Action( Messages.get( "Main.menu.insert.link" ), "Shortcut+L", LINK,
+ e -> getActiveEditor().insertLink(),
+ activeFileEditorIsNull );
+ Action insertImageAction = new Action( Messages.get( "Main.menu.insert.image" ), "Shortcut+G", PICTURE_ALT,
+ e -> getActiveEditor().insertImage(),
+ activeFileEditorIsNull );
+
+ final Action[] headers = new Action[ 6 ];
+
+ // Insert header actions (H1 ... H6)
+ for( int i = 1; i <= 6; i++ ) {
+ final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
+ final String markup = String.format( "\n\n%s ", hashes );
+ final String text = Messages.get( "Main.menu.insert.header_" + i );
+ final String accelerator = "Shortcut+" + i;
+ final String prompt = Messages.get( "Main.menu.insert.header_" + i + ".prompt" );
+
+ headers[ i - 1 ] = new Action( text, accelerator, HEADER,
+ e -> getActiveEditor().surroundSelection( markup, "", prompt ),
+ activeFileEditorIsNull );
+ }
+
+ Action insertUnorderedListAction = new Action( Messages.get( "Main.menu.insert.unordered_list" ), "Shortcut+U", LIST_UL,
+ e -> getActiveEditor().surroundSelection( "\n\n* ", "" ),
+ activeFileEditorIsNull );
+ Action insertOrderedListAction = new Action( Messages.get( "Main.menu.insert.ordered_list" ), "Shortcut+Shift+O", LIST_OL,
+ e -> getActiveEditor().surroundSelection( "\n\n1. ", "" ),
+ activeFileEditorIsNull );
+ Action insertHorizontalRuleAction = new Action( Messages.get( "Main.menu.insert.horizontal_rule" ), "Shortcut+H", null,
+ e -> getActiveEditor().surroundSelection( "\n\n---\n\n", "" ),
+ activeFileEditorIsNull );
+
+ // Help actions
+ Action helpAboutAction = new Action( Messages.get( "Main.menu.help.about" ), null, null, e -> helpAbout() );
+
+ //---- MenuBar ----
+ Menu fileMenu = ActionUtils.createMenu( Messages.get( "Main.menu.file" ),
+ fileNewAction,
+ fileOpenAction,
+ null,
+ fileCloseAction,
+ fileCloseAllAction,
+ null,
+ fileSaveAction,
+ fileSaveAllAction,
+ null,
+ fileExitAction );
+
+ Menu editMenu = ActionUtils.createMenu( Messages.get( "Main.menu.edit" ),
+ editUndoAction,
+ editRedoAction );
+
+ Menu insertMenu = ActionUtils.createMenu( Messages.get( "Main.menu.insert" ),
+ insertBoldAction,
+ insertItalicAction,
+ insertStrikethroughAction,
+ insertBlockquoteAction,
+ insertCodeAction,
+ insertFencedCodeBlockAction,
+ null,
+ insertLinkAction,
+ insertImageAction,
+ null,
+ headers[ 0 ],
+ headers[ 1 ],
+ headers[ 2 ],
+ headers[ 3 ],
+ headers[ 4 ],
+ headers[ 5 ],
+ null,
+ insertUnorderedListAction,
+ insertOrderedListAction,
+ insertHorizontalRuleAction );
+
+ Menu helpMenu = ActionUtils.createMenu( Messages.get( "Main.menu.help" ),
+ helpAboutAction );
+
+ menuBar = new MenuBar( fileMenu, editMenu, insertMenu, helpMenu );
//---- ToolBar ----
src/main/java/com/scrivenvar/editor/EditorPane.java
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.ReadOnlyDoubleProperty;
-import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
private StyleClassedTextArea editor;
private VirtualizedScrollPane<StyleClassedTextArea> scrollPane;
- private final ReadOnlyDoubleWrapper scrollY = new ReadOnlyDoubleWrapper();
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
-
- private String lineSeparator = getLineSeparator();
/**
public String getText() {
- String text = getEditor().getText();
-
- if( !this.lineSeparator.equals( "\n" ) ) {
- text = text.replace( "\n", this.lineSeparator );
- }
-
- return text;
+ return getEditor().getText();
}
public void setText( final String text ) {
- this.lineSeparator = determineLineSeparator( text );
getEditor().deselect();
getEditor().replaceText( text );
protected StyleClassedTextArea createTextArea() {
return new StyleClassedTextArea( false );
- }
-
- public double getScrollY() {
- return this.scrollY.get();
- }
-
- protected void setScrollY( double scrolled ) {
- this.scrollY.set( scrolled );
- }
-
- public ReadOnlyDoubleProperty scrollYProperty() {
- return this.scrollY.getReadOnlyProperty();
}
public ObjectProperty<Path> pathProperty() {
return this.path;
- }
-
- private String getLineSeparator() {
- final String separator = getOptions().getLineSeparator();
-
- return (separator != null)
- ? separator
- : System.lineSeparator();
- }
-
- private String determineLineSeparator( final String s ) {
- final int length = s.length();
-
- // TODO: Looping backwards will probably detect a newline sooner.
- for( int i = 0; i < length; i++ ) {
- char ch = s.charAt( i );
- if( ch == '\n' ) {
- return (i > 0 && s.charAt( i - 1 ) == '\r') ? "\r\n" : "\n";
- }
- }
-
- return getLineSeparator();
}
}
src/main/java/com/scrivenvar/editor/MarkdownEditorPane.java
import com.scrivenvar.dialogs.LinkDialog;
import com.scrivenvar.processors.MarkdownProcessor;
-import com.scrivenvar.util.Utils;
+import static com.scrivenvar.util.Utils.ltrim;
+import static com.scrivenvar.util.Utils.rtrim;
import com.vladsch.flexmark.ast.Link;
import com.vladsch.flexmark.ast.Node;
// remove leading whitespaces from leading text if selection starts at zero
if( start == 0 ) {
- leading = Utils.ltrim( leading );
+ leading = ltrim( leading );
}
// remove trailing whitespaces from trailing text if selection ends at text end
if( end == textArea.getLength() ) {
- trailing = Utils.rtrim( trailing );
+ trailing = rtrim( trailing );
}
src/main/java/com/scrivenvar/editor/VariableNameInjector.java
import static org.fxmisc.wellbehaved.event.InputMap.consume;
import static org.fxmisc.wellbehaved.event.InputMap.sequence;
+import static com.scrivenvar.definition.Lists.getFirst;
+import static com.scrivenvar.definition.Lists.getLast;
+import static java.lang.Character.isSpaceChar;
+import static java.lang.Character.isWhitespace;
+import static java.lang.Math.min;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
+import static org.fxmisc.wellbehaved.event.InputMap.consume;
+import static com.scrivenvar.definition.Lists.getFirst;
+import static com.scrivenvar.definition.Lists.getLast;
+import static java.lang.Character.isSpaceChar;
+import static java.lang.Character.isWhitespace;
+import static java.lang.Math.min;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
+import static org.fxmisc.wellbehaved.event.InputMap.consume;
+import static com.scrivenvar.definition.Lists.getFirst;
+import static com.scrivenvar.definition.Lists.getLast;
+import static java.lang.Character.isSpaceChar;
+import static java.lang.Character.isWhitespace;
+import static java.lang.Math.min;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
+import static org.fxmisc.wellbehaved.event.InputMap.consume;
+import static com.scrivenvar.definition.Lists.getFirst;
+import static com.scrivenvar.definition.Lists.getLast;
+import static java.lang.Character.isSpaceChar;
+import static java.lang.Character.isWhitespace;
+import static java.lang.Math.min;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
+import static org.fxmisc.wellbehaved.event.InputMap.consume;
+import static com.scrivenvar.definition.Lists.getFirst;
+import static com.scrivenvar.definition.Lists.getLast;
+import static java.lang.Character.isSpaceChar;
+import static java.lang.Character.isWhitespace;
+import static java.lang.Math.min;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
+import static org.fxmisc.wellbehaved.event.InputMap.consume;
+import static com.scrivenvar.definition.Lists.getFirst;
+import static com.scrivenvar.definition.Lists.getLast;
+import static java.lang.Character.isSpaceChar;
+import static java.lang.Character.isWhitespace;
+import static java.lang.Math.min;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
+import static org.fxmisc.wellbehaved.event.InputMap.consume;
+import static com.scrivenvar.definition.Lists.getFirst;
+import static com.scrivenvar.definition.Lists.getLast;
+import static java.lang.Character.isSpaceChar;
+import static java.lang.Character.isWhitespace;
+import static java.lang.Math.min;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
+import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
+import static org.fxmisc.wellbehaved.event.InputMap.consume;
/**
src/main/java/com/scrivenvar/options/GeneralOptionsPane.java
-/*
- * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * o Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * o Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.scrivenvar.options;
-
-import com.scrivenvar.Messages;
-import com.scrivenvar.ui.AbstractPane;
-import com.scrivenvar.util.Item;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.SortedMap;
-import javafx.scene.control.ComboBox;
-import javafx.scene.control.Label;
-
-/**
- * General options pane.
- *
- * @author Karl Tauber
- */
-public class GeneralOptionsPane extends AbstractPane {
-
- @SuppressWarnings( "unchecked" )
- public GeneralOptionsPane() {
- initComponents();
-
- String defaultLineSeparator = System.getProperty( "line.separator", "\n" );
- String defaultLineSeparatorStr = defaultLineSeparator.replace( "\r", "CR" ).replace( "\n", "LF" );
- lineSeparatorField.getItems().addAll(
- new Item<>( Messages.get( "GeneralOptionsPane.platformDefault", defaultLineSeparatorStr ), null ),
- new Item<>( Messages.get( "GeneralOptionsPane.sepWindows" ), "\r\n" ),
- new Item<>( Messages.get( "GeneralOptionsPane.sepUnix" ), "\n" ) );
-
- encodingField.getItems().addAll( getAvailableEncodings() );
- }
-
- private Collection<Item<String>> getAvailableEncodings() {
- SortedMap<String, Charset> availableCharsets = Charset.availableCharsets();
-
- ArrayList<Item<String>> values = new ArrayList<>( 1 + availableCharsets.size() );
- values.add( new Item<>( Messages.get( "GeneralOptionsPane.platformDefault", Charset.defaultCharset().name() ), null ) );
-
- for( String name : availableCharsets.keySet() ) {
- values.add( new Item<>( name, name ) );
- }
-
- return values;
- }
-
- void load() {
- lineSeparatorField.setValue( new Item<>( getOptions().getLineSeparator(), getOptions().getLineSeparator() ) );
- encodingField.setValue( new Item<>( getOptions().getEncoding(), getOptions().getEncoding() ) );
-
- }
-
- void save() {
- getOptions().setLineSeparator( lineSeparatorField.getValue().value );
- getOptions().setEncoding( encodingField.getValue().value );
-
- }
-
- private void initComponents() {
- // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
- Label lineSeparatorLabel = new Label();
- lineSeparatorField = new ComboBox<>();
- Label lineSeparatorLabel2 = new Label();
- Label encodingLabel = new Label();
- encodingField = new ComboBox<>();
-
- //======== this ========
- setCols( "[fill][fill][fill]" );
- setRows( "[][]para[]" );
-
- //---- lineSeparatorLabel ----
- lineSeparatorLabel.setText( Messages.get( "GeneralOptionsPane.lineSeparatorLabel.text" ) );
- lineSeparatorLabel.setMnemonicParsing( true );
- add( lineSeparatorLabel, "cell 0 0" );
- add( lineSeparatorField, "cell 1 0" );
-
- //---- lineSeparatorLabel2 ----
- lineSeparatorLabel2.setText( Messages.get( "GeneralOptionsPane.lineSeparatorLabel2.text" ) );
- add( lineSeparatorLabel2, "cell 2 0" );
-
- //---- encodingLabel ----
- encodingLabel.setText( Messages.get( "GeneralOptionsPane.encodingLabel.text" ) );
- encodingLabel.setMnemonicParsing( true );
- add( encodingLabel, "cell 0 1" );
-
- //---- encodingField ----
- encodingField.setVisibleRowCount( 20 );
- add( encodingField, "cell 1 1" );
- // JFormDesigner - End of component initialization //GEN-END:initComponents
-
- // TODO set this in JFormDesigner as soon as it supports labelFor
- lineSeparatorLabel.setLabelFor( lineSeparatorField );
- encodingLabel.setLabelFor( encodingField );
- }
-
- // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
- private ComboBox<Item<String>> lineSeparatorField;
- private ComboBox<Item<String>> encodingField;
- // JFormDesigner - End of variables declaration //GEN-END:variables
-}
src/main/java/com/scrivenvar/options/GeneralOptionsPane.jfd
-JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8"
-
-new FormModel {
- "i18n.bundlePackage": "com.scrivendor"
- "i18n.bundleName": "messages"
- "i18n.autoExternalize": true
- "i18n.keyPrefix": "GeneralOptionsPane"
- contentType: "form/javafx"
- root: new FormRoot {
- add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) {
- "$layoutConstraints": ""
- "$columnConstraints": "[fill][fill][fill]"
- "$rowConstraints": "[][]para[]"
- } ) {
- name: "this"
- add( new FormComponent( "javafx.scene.control.Label" ) {
- name: "lineSeparatorLabel"
- "text": new FormMessage( null, "GeneralOptionsPane.lineSeparatorLabel.text" )
- "mnemonicParsing": true
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 0"
- } )
- add( new FormComponent( "javafx.scene.control.ComboBox" ) {
- name: "lineSeparatorField"
- auxiliary() {
- "JavaCodeGenerator.typeParameters": "Item<String>"
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 1 0"
- } )
- add( new FormComponent( "javafx.scene.control.Label" ) {
- name: "lineSeparatorLabel2"
- "text": new FormMessage( null, "GeneralOptionsPane.lineSeparatorLabel2.text" )
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 2 0"
- } )
- add( new FormComponent( "javafx.scene.control.Label" ) {
- name: "encodingLabel"
- "text": new FormMessage( null, "GeneralOptionsPane.encodingLabel.text" )
- "mnemonicParsing": true
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 1"
- } )
- add( new FormComponent( "javafx.scene.control.ComboBox" ) {
- name: "encodingField"
- "visibleRowCount": 20
- auxiliary() {
- "JavaCodeGenerator.typeParameters": "Item<String>"
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 1 1"
- } )
- }, new FormLayoutConstraints( null ) {
- "location": new javafx.geometry.Point2D( 0.0, 0.0 )
- "size": new javafx.geometry.Dimension2D( 400.0, 300.0 )
- } )
- }
-}
src/main/java/com/scrivenvar/options/MarkdownOptionsPane.jfd
-JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8"
-
-new FormModel {
- "i18n.bundlePackage": "com.scrivendor"
- "i18n.bundleName": "messages"
- "i18n.autoExternalize": true
- "i18n.keyPrefix": "MarkdownOptionsPane"
- contentType: "form/javafx"
- root: new FormRoot {
- add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) {
- "$rowConstraints": "[][][][][][][][][][][][][][][][][][]"
- "$columnConstraints": "[][fill]"
- } ) {
- name: "this"
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "smartsExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.smartsExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 0"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "quotesExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.quotesExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 1"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "abbreviationsExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.abbreviationsExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 2"
- } )
- add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
- name: "abbreviationsExtLink"
- "text": new FormMessage( null, "MarkdownOptionsPane.abbreviationsExtLink.text" )
- "uri": "http://michelf.com/projects/php-markdown/extra/#abbr"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 2,gapx 0"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "hardwrapsExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.hardwrapsExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 3"
- } )
- add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
- name: "hardwrapsExtLink"
- "text": new FormMessage( null, "MarkdownOptionsPane.hardwrapsExtLink.text" )
- "uri": "https://help.github.com/articles/writing-on-github/#markup"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 3,gapx 0"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "autolinksExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.autolinksExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 4"
- } )
- add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
- name: "autolinksExtLink"
- "text": new FormMessage( null, "MarkdownOptionsPane.autolinksExtLink.text" )
- "uri": "https://help.github.com/articles/github-flavored-markdown/#url-autolinking"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 4,gapx 0"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "tablesExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.tablesExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 5"
- } )
- add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
- name: "tablesExtLink"
- "text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLink.text" )
- "uri": "http://fletcher.github.io/MultiMarkdown-4/syntax.html#tables"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 5,gapx 0"
- } )
- add( new FormComponent( "javafx.scene.control.Label" ) {
- name: "tablesExtLabel"
- "text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLabel.text" )
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 5,gapx 3"
- } )
- add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
- name: "tablesExtLink2"
- "text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLink2.text" )
- "uri": "https://michelf.ca/projects/php-markdown/extra/#table"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 5,gapx 3 3"
- } )
- add( new FormComponent( "javafx.scene.control.Label" ) {
- name: "tablesExtLabel2"
- "text": new FormMessage( null, "MarkdownOptionsPane.tablesExtLabel2.text" )
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 5,gapx 0"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "definitionListsExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.definitionListsExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 6"
- } )
- add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
- name: "definitionListsExtLink"
- "text": new FormMessage( null, "MarkdownOptionsPane.definitionListsExtLink.text" )
- "uri": "https://michelf.ca/projects/php-markdown/extra/#def-list"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 6,gapx 0"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "fencedCodeBlocksExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 7"
- } )
- add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
- name: "fencedCodeBlocksExtLink"
- "text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtLink.text" )
- "uri": "http://michelf.com/projects/php-markdown/extra/#fenced-code-blocks"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 7,gapx 0"
- } )
- add( new FormComponent( "javafx.scene.control.Label" ) {
- name: "fencedCodeBlocksExtLabel"
- "text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtLabel.text" )
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 7,gapx 3"
- } )
- add( new FormComponent( "com.scrivendor.controls.WebHyperlink" ) {
- name: "fencedCodeBlocksExtLink2"
- "text": new FormMessage( null, "MarkdownOptionsPane.fencedCodeBlocksExtLink2.text" )
- "uri": "https://help.github.com/articles/github-flavored-markdown/#fenced-code-blocks"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": true
- }
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 7,gapx 3"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "wikilinksExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.wikilinksExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 8"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "strikethroughExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.strikethroughExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 9"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "anchorlinksExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.anchorlinksExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 10"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "suppressHtmlBlocksExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.suppressHtmlBlocksExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 11"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "suppressInlineHtmlExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.suppressInlineHtmlExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 12"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "atxHeaderSpaceExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.atxHeaderSpaceExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 13"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "forceListItemParaExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.forceListItemParaExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 14"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "relaxedHrRulesExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.relaxedHrRulesExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 15"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "taskListItemsExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.taskListItemsExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 16"
- } )
- add( new FormComponent( "com.scrivendor.controls.FlagCheckBox" ) {
- name: "extAnchorLinksExtCheckBox"
- "text": new FormMessage( null, "MarkdownOptionsPane.extAnchorLinksExtCheckBox.text" )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 17"
- } )
- }, new FormLayoutConstraints( null ) {
- "location": new javafx.geometry.Point2D( 0.0, 0.0 )
- "size": new javafx.geometry.Dimension2D( 600.0, 531.0 )
- } )
- }
-}
src/main/java/com/scrivenvar/options/OptionsDialog.java
-/*
- * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * o Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * o Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.scrivenvar.options;
-
-import com.scrivenvar.Messages;
-import com.scrivenvar.Services;
-import com.scrivenvar.service.Options;
-import com.scrivenvar.service.events.impl.ButtonOrderPane;
-import java.util.prefs.Preferences;
-import javafx.event.ActionEvent;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.Dialog;
-import javafx.scene.control.DialogPane;
-import javafx.scene.control.Tab;
-import javafx.scene.control.TabPane;
-import javafx.stage.Window;
-
-/**
- * Options dialog.
- *
- * @author Karl Tauber and White Magic Software, Ltd.
- */
-public class OptionsDialog extends Dialog<Void> {
-
- private final Options options = Services.load( Options.class );
-
- public OptionsDialog( Window owner ) {
- setTitle( Messages.get( "OptionsDialog.title" ) );
- initOwner( owner );
-
- initComponents();
-
- tabPane.getStyleClass().add( TabPane.STYLE_CLASS_FLOATING );
-
- setDialogPane( new ButtonOrderPane() );
-
- final DialogPane dialogPane = getDialogPane();
- dialogPane.setContent( tabPane );
- dialogPane.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL );
-
- dialogPane.lookupButton( ButtonType.OK ).addEventHandler( ActionEvent.ACTION, e -> {
- save();
- e.consume();
- } );
-
- // load options
- load();
-
- // select last tab
- int tabIndex = getState().getInt( "lastOptionsTab", -1 );
- if( tabIndex > 0 ) {
- tabPane.getSelectionModel().select( tabIndex );
- }
-
- // remember last selected tab
- setOnHidden( e -> {
- getState().putInt( "lastOptionsTab", tabPane.getSelectionModel().getSelectedIndex() );
- } );
- }
-
- private Options getOptions() {
- return options;
- }
-
- private Preferences getState() {
- return getOptions().getState();
- }
-
- private void load() {
- generalOptionsPane.load();
- }
-
- private void save() {
- generalOptionsPane.save();
- Services.load( Options.class ).save();
- }
-
- private void initComponents() {
- // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
- tabPane = new TabPane();
- generalTab = new Tab();
- generalOptionsPane = new GeneralOptionsPane();
-
- //======== tabPane ========
- {
- tabPane.setTabClosingPolicy( TabPane.TabClosingPolicy.UNAVAILABLE );
-
- //======== generalTab ========
- {
- generalTab.setText( Messages.get( "OptionsDialog.generalTab.text" ) );
- generalTab.setContent( generalOptionsPane );
- }
-
- tabPane.getTabs().addAll( generalTab );
- }
- // JFormDesigner - End of component initialization //GEN-END:initComponents
- }
-
- // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
- private TabPane tabPane;
- private Tab generalTab;
- private GeneralOptionsPane generalOptionsPane;
- // JFormDesigner - End of variables declaration //GEN-END:variables
-}
src/main/java/com/scrivenvar/options/OptionsDialog.jfd
-JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8"
-
-new FormModel {
- "i18n.bundlePackage": "com.scrivendor"
- "i18n.bundleName": "messages"
- "i18n.keyPrefix": "OptionsDialog"
- "i18n.autoExternalize": true
- contentType: "form/javafx"
- root: new FormRoot {
- add( new FormContainer( "javafx.scene.control.TabPane", new FormLayoutManager( class javafx.scene.control.TabPane ) ) {
- name: "tabPane"
- "tabClosingPolicy": enum javafx.scene.control.TabPane$TabClosingPolicy UNAVAILABLE
- add( new FormContainer( "javafx.scene.control.Tab", new FormLayoutManager( class javafx.scene.control.Tab ) ) {
- name: "generalTab"
- "text": new FormMessage( null, "OptionsDialog.generalTab.text" )
- add( new FormComponent( "com.scrivendor.options.GeneralOptionsPane" ) {
- name: "generalOptionsPane"
- } )
- } )
- add( new FormContainer( "javafx.scene.control.Tab", new FormLayoutManager( class javafx.scene.control.Tab ) ) {
- name: "markdownTab"
- "text": new FormMessage( null, "OptionsDialog.markdownTab.text" )
- add( new FormComponent( "com.scrivendor.options.MarkdownOptionsPane" ) {
- name: "markdownOptionsPane"
- } )
- } )
- }, new FormLayoutConstraints( null ) {
- "location": new javafx.geometry.Point2D( 0.0, 0.0 )
- "size": new javafx.geometry.Dimension2D( 600.0, 560.0 )
- } )
- }
-}
src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
import javafx.concurrent.Worker.State;
import static javafx.concurrent.Worker.State.SUCCEEDED;
+import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.scene.web.WebEngine;
setPath( path );
initListeners();
+ initTraversal();
+ }
- // Prevent tabbing into the preview pane.
- getWebView().setFocusTraversable( false );
+ /**
+ * Initializes observers for document changes. When the document is reloaded
+ * with new HTML, this triggers a scroll event that repositions the document
+ * to the injected caret (that corresponds with the position in the text
+ * editor).
+ */
+ private void initListeners() {
+ // Scrolls to the caret after the content has been loaded.
+ getEngine().getLoadWorker().stateProperty().addListener(
+ (ObservableValue<? extends State> observable,
+ final State oldValue, final State newValue) -> {
+ if( newValue == SUCCEEDED ) {
+ scrollToCaret();
+ }
+ } );
+ }
+
+ /**
+ * Ensures images can be found relative to the document.
+ *
+ * @return The base path element to use for the document, or the empty string
+ * if no path has been set, yet.
+ */
+ private String getBase() {
+ final Path basePath = getPath();
+
+ return basePath == null
+ ? ""
+ : ("<base href='" + basePath.getParent().toUri().toString() + "'>");
}
/**
* Updates the internal HTML source, loads it into the preview pane, then
* scrolls to the caret position.
*
- * @param html
+ * @param html The new HTML document to display.
*/
public void update( final String html ) {
- setHtml( html );
- update();
- }
-
- private void update() {
getEngine().loadContent(
"<!DOCTYPE html>"
+ "<html>"
+ "<head>"
+ "<link rel='stylesheet' href='" + getClass().getResource( "webview.css" ) + "'>"
+ getBase()
+ "</head>"
+ "<body>"
- + getHtml()
+ + html
+ "</body>"
+ "</html>" );
- }
-
- private String getBase() {
- final Path basePath = getPath();
-
- return basePath == null
- ? ""
- : ("<base href='" + basePath.getParent().toUri().toString() + "'>");
- }
-
- /**
- * Initializes observers for document changes. When the document is reloaded
- * with new HTML, this triggers a scroll event that repositions the document
- * to the injected caret (that corresponds with the position in the text
- * editor).
- */
- private void initListeners() {
- // Scrolls to the caret after the content has been loaded.
- getEngine().getLoadWorker().stateProperty().addListener(
- (ObservableValue<? extends State> observable,
- State oldValue, State newValue) -> {
- if( newValue == SUCCEEDED ) {
- scrollToCaret();
- }
- } );
}
+ " window.scrollTo( 0, e.topOffset() - (window.innerHeight / 2 ) );"
+ "}";
+ }
+
+ /**
+ * Prevent tabbing into the preview pane.
+ */
+ private void initTraversal() {
+ getWebView().setFocusTraversable( false );
}
}
- public WebView getWebView() {
+ private WebView getWebView() {
return this.webView;
- }
-
- private String getHtml() {
- return this.html;
- }
-
- private void setHtml( final String html ) {
- this.html = html;
}
private Path getPath() {
return this.path;
}
private void setPath( final Path path ) {
this.path = path;
+ }
+
+ public Node getNode() {
+ return getWebView();
}
}
src/main/java/com/scrivenvar/service/Options.java
import java.util.prefs.Preferences;
-import javafx.beans.property.StringProperty;
/**
- * Options
+ * Responsible for persistent options.
*
* @author White Magic Software, Ltd.
*/
public interface Options {
-
- public Preferences getState();
-
- public void load( Preferences options );
-
- public void save();
-
- public String getLineSeparator();
-
- public void setLineSeparator( String lineSeparator );
-
- public StringProperty lineSeparatorProperty();
-
- public String getEncoding();
- public void setEncoding( String encoding );
+ public Preferences getState();
+
+ /**
+ * Stores the key and value into the user preferences to be loaded the next
+ * time the application is launched.
+ *
+ * @param key Name of the key to persist along with its value.
+ * @param value Value to associate with the key.
+ */
+ public void put( String key, String value );
- public StringProperty encodingProperty();
+ /**
+ * Retrieves the value for a key in the user preferences.
+ *
+ * @param key Retrieve the value of this key.
+ * @param defaultValue The value to return in the event that the given key has
+ * no associated value.
+ *
+ * @return The value associated with the key.
+ */
+ public String get( String key, String defaultValue );
}
src/main/java/com/scrivenvar/service/impl/DefaultOptions.java
import com.scrivenvar.service.Options;
-import static com.scrivenvar.util.Utils.putPrefs;
import java.util.prefs.Preferences;
import static java.util.prefs.Preferences.userRoot;
-import javafx.beans.property.SimpleStringProperty;
-import javafx.beans.property.StringProperty;
/**
* Persistent options user can change at runtime.
*
* @author Karl Tauber and White Magic Software, Ltd.
*/
public class DefaultOptions implements Options {
- private final StringProperty LINE_SEPARATOR = new SimpleStringProperty();
- private final StringProperty ENCODING = new SimpleStringProperty();
-
private Preferences preferences;
public DefaultOptions() {
setPreferences( getRootPreferences().node( "options" ) );
+ }
+
+ @Override
+ public void put( final String key, final String value ) {
+ getPreferences().put( key, value );
}
- private void setPreferences( Preferences preferences ) {
+ @Override
+ public String get( final String key, final String defalutValue ) {
+ return getPreferences().get( key, defalutValue );
+ }
+
+ private void setPreferences( final Preferences preferences ) {
this.preferences = preferences;
}
}
- public Preferences getPreferences() {
+ private Preferences getPreferences() {
return this.preferences;
- }
-
- @Override
- public void load( Preferences options ) {
- setLineSeparator( options.get( "lineSeparator", null ) );
- setEncoding( options.get( "encoding", null ) );
- }
-
- @Override
- public void save() {
- final Preferences prefs = getPreferences();
-
- putPrefs( prefs, "lineSeparator", getLineSeparator(), null );
- putPrefs( prefs, "encoding", getEncoding(), null );
- }
-
- @Override
- public String getLineSeparator() {
- return LINE_SEPARATOR.get();
- }
-
- @Override
- public void setLineSeparator( String lineSeparator ) {
- LINE_SEPARATOR.set( lineSeparator );
- }
-
- @Override
- public StringProperty lineSeparatorProperty() {
- return LINE_SEPARATOR;
- }
-
- @Override
- public String getEncoding() {
- return ENCODING.get();
- }
-
- @Override
- public void setEncoding( String encoding ) {
- ENCODING.set( encoding );
- }
-
- @Override
- public StringProperty encodingProperty() {
- return ENCODING;
}
}
src/main/java/com/scrivenvar/util/Item.java
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-
package com.scrivenvar.util;
/**
- * Simple item for a ChoiceBox, ComboBox or ListView.
- * Consists of a string name and a value object.
- * toString() returns the name.
- * equals() compares the value and hashCode() returns the hash code of the value.
+ * Simple item for a ChoiceBox, ComboBox or ListView. Consists of a string name
+ * and a value object. toString() returns the name. equals() compares the value
+ * and hashCode() returns the hash code of the value.
*
* @author Karl Tauber
+ * @param <V> The type of item value.
*/
-public class Item<V>
-{
- public final String name;
- public final V value;
+public class Item<V> {
- public Item(String name, V value) {
- this.name = name;
- this.value = value;
- }
+ public final String name;
+ public final V value;
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (!(obj instanceof Item))
- return false;
- return Utils.safeEquals(value, ((Item<?>)obj).value);
- }
+ public Item( final String name, final V value ) {
+ this.name = name;
+ this.value = value;
+ }
- @Override
- public int hashCode() {
- return (value != null) ? value.hashCode() : 0;
- }
+ @Override
+ public boolean equals( final Object obj ) {
+ if( this == obj ) {
+ return true;
+ }
+ if( !(obj instanceof Item) ) {
+ return false;
+ }
+ return Utils.safeEquals( value, ((Item<?>)obj).value );
+ }
- @Override
- public String toString() {
- return name;
- }
+ @Override
+ public int hashCode() {
+ return (value != null) ? value.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
}
src/main/java/com/scrivenvar/util/Utils.java
import java.util.ArrayList;
-import java.util.Set;
import java.util.prefs.Preferences;
-import javafx.geometry.Orientation;
-import javafx.scene.Node;
-import javafx.scene.control.ScrollBar;
/**
* @author Karl Tauber
*/
public class Utils {
- public static boolean safeEquals( Object o1, Object o2 ) {
+ public static boolean safeEquals( final Object o1, final Object o2 ) {
if( o1 == o2 ) {
return true;
}
if( o1 == null || o2 == null ) {
return false;
}
return o1.equals( o2 );
}
- public static boolean isNullOrEmpty( String s ) {
+ public static boolean isNullOrEmpty( final String s ) {
return s == null || s.isEmpty();
}
public static String[] getPrefsStrings( final Preferences prefs, String key ) {
- final ArrayList<String> arr = new ArrayList<>();
+ final ArrayList<String> arr = new ArrayList<>( 256 );
for( int i = 0; i < 10000; i++ ) {
final String s = prefs.get( key + (i + 1), null );
if( s == null ) {
break;
}
+
arr.add( s );
}
for( int i = strings.length; prefs.get( key + (i + 1), null ) != null; i++ ) {
prefs.remove( key + (i + 1) );
- }
- }
-
- public static ScrollBar findVScrollBar( Node node ) {
- final Set<Node> scrollBars = node.lookupAll( ".scroll-bar" );
-
- for( final Node scrollBar : scrollBars ) {
- if( scrollBar instanceof ScrollBar
- && ((ScrollBar)scrollBar).getOrientation() == Orientation.VERTICAL ) {
- return (ScrollBar)scrollBar;
- }
}
-
- return null;
}
}
src/main/resources/com/scrivenvar/build.sh
+#!/bin/bash
+
+INKSCAPE=/usr/bin/inkscape
+
+declare -a SIZES=("16" "32" "64" "128" "256" "512")
+
+for i in "${SIZES[@]}"; do
+ # -y: export background opacity 0
+ $INKSCAPE -y 0 -z -f "logo.svg" -w "${i}" -e "logo${i}.png"
+done
+
Delta945 lines added, 1431 lines removed, 486-line decrease