| Author | djarvis <email> |
|---|---|
| Date | 2016-10-20 00:11:25 GMT-0700 |
| Commit | da1354de143ae2b562bc3f7ed04fcbd3cccfc866 |
| Parent | 1b19e46 |
| */ | ||
| public class Constants { | ||
| - | ||
| + | ||
| /** | ||
| * Prevent instantiation. | ||
| */ | ||
| private Constants() { | ||
| - | ||
| } | ||
| package com.scrivendor; | ||
| +import com.scrivendor.editor.MarkdownEditorPane; | ||
| +import com.scrivendor.preview.MarkdownPreviewPane; | ||
| +import com.scrivendor.service.Options; | ||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
| import javafx.scene.text.Text; | ||
| import org.fxmisc.undo.UndoManager; | ||
| -import com.scrivendor.editor.MarkdownEditorPane; | ||
| -import com.scrivendor.preview.MarkdownPreviewPane; | ||
| -import com.scrivendor.service.Options; | ||
| /** | ||
| * Editor for a single file. | ||
| * | ||
| * @author Karl Tauber | ||
| */ | ||
| class FileEditor { | ||
| - | ||
| + | ||
| private final Options options = Services.load( Options.class ); | ||
| canRedo.bind( undoManager.redoAvailableProperty() ); | ||
| - SplitPane splitPane = new SplitPane( markdownEditorPane.getNode(), markdownPreviewPane.getNode() ); | ||
| + SplitPane splitPane = new SplitPane( | ||
| + markdownEditorPane.getNode(), | ||
| + markdownPreviewPane.getNode() ); | ||
| tab.setContent( splitPane ); | ||
| try { | ||
| - bytes = markdown.getBytes(getOptions().getEncoding() ); | ||
| + bytes = markdown.getBytes( getOptions().getEncoding() ); | ||
| } catch( Exception ex ) { | ||
| bytes = markdown.getBytes(); | ||
| } | ||
| } | ||
| - | ||
| + | ||
| private Options getOptions() { | ||
| return this.options; | ||
| package com.scrivendor; | ||
| +import com.scrivendor.service.Settings; | ||
| +import com.scrivendor.ui.AbstractPane; | ||
| +import com.scrivendor.util.Utils; | ||
| import java.io.File; | ||
| import java.nio.file.Path; | ||
| import javafx.stage.FileChooser; | ||
| import javafx.stage.FileChooser.ExtensionFilter; | ||
| -import com.scrivendor.service.Settings; | ||
| -import com.scrivendor.ui.AbstractPane; | ||
| -import com.scrivendor.util.Utils; | ||
| /** | ||
| /** | ||
| - * Recursively resolves message properties. Property values can refer | ||
| - * to other properties using a <code>${var}</code> syntax. | ||
| + * Recursively resolves message properties. Property values can refer to other | ||
| + * properties using a <code>${var}</code> syntax. | ||
| * | ||
| * @author Karl Tauber, Dave Jarvis | ||
| */ | ||
| private static String resolve( ResourceBundle props, String s ) { | ||
| + final int len = s.length(); | ||
| + final Stack<StringBuilder> stack = new Stack<>(); | ||
| + | ||
| StringBuilder sb = new StringBuilder( 256 ); | ||
| - Stack<StringBuilder> stack = new Stack<>(); | ||
| - int len = s.length(); | ||
| + boolean open = false; | ||
| for( int i = 0; i < len; i++ ) { | ||
| - char c = s.charAt( i ); | ||
| + final char c = s.charAt( i ); | ||
| switch( c ) { | ||
| case '$': { | ||
| if( i + 1 < len && s.charAt( i + 1 ) == '{' ) { | ||
| stack.push( sb ); | ||
| sb = new StringBuilder( 256 ); | ||
| i++; | ||
| + open = true; | ||
| } | ||
| + | ||
| break; | ||
| } | ||
| case '}': { | ||
| - if( stack.isEmpty() ) { | ||
| - throw new IllegalArgumentException( "unexpected '}'" ); | ||
| - } | ||
| - | ||
| - String name = sb.toString(); | ||
| + if( open ) { | ||
| + open = false; | ||
| + final String name = sb.toString(); | ||
| - sb = stack.pop(); | ||
| - sb.append( props.getString( name ) ); | ||
| - break; | ||
| + sb = stack.pop(); | ||
| + sb.append( props.getString( name ) ); | ||
| + break; | ||
| + } | ||
| } | ||
| default: { | ||
| sb.append( c ); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| - if( !stack.isEmpty() ) { | ||
| + if( open ) { | ||
| throw new IllegalArgumentException( "missing '}'" ); | ||
| } | ||
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| */ | ||
| - | ||
| package com.scrivendor.dialogs; | ||
| * @author Karl Tauber | ||
| */ | ||
| -public class ImageDialog | ||
| - extends Dialog<String> | ||
| -{ | ||
| - private final StringProperty image = new SimpleStringProperty(); | ||
| +public class ImageDialog extends Dialog<String> { | ||
| - public ImageDialog(Window owner, Path basePath) { | ||
| - setTitle(Messages.get("ImageDialog.title")); | ||
| - initOwner(owner); | ||
| - setResizable(true); | ||
| + private final StringProperty image = new SimpleStringProperty(); | ||
| - initComponents(); | ||
| + public ImageDialog( Window owner, Path basePath ) { | ||
| + setTitle( Messages.get( "ImageDialog.title" ) ); | ||
| + initOwner( owner ); | ||
| + setResizable( true ); | ||
| - linkBrowseFileButton.setBasePath(basePath); | ||
| - linkBrowseFileButton.addExtensionFilter(new ExtensionFilter(Messages.get("ImageDialog.chooser.imagesFilter"), "*.png", "*.gif", "*.jpg")); | ||
| - linkBrowseFileButton.urlProperty().bindBidirectional(urlField.escapedTextProperty()); | ||
| + initComponents(); | ||
| - DialogPane dialogPane = getDialogPane(); | ||
| - dialogPane.setContent(pane); | ||
| - dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); | ||
| + linkBrowseFileButton.setBasePath( basePath ); | ||
| + linkBrowseFileButton.addExtensionFilter( new ExtensionFilter( Messages.get( "ImageDialog.chooser.imagesFilter" ), "*.png", "*.gif", "*.jpg" ) ); | ||
| + linkBrowseFileButton.urlProperty().bindBidirectional( urlField.escapedTextProperty() ); | ||
| - dialogPane.lookupButton(ButtonType.OK).disableProperty().bind( | ||
| - urlField.escapedTextProperty().isEmpty() | ||
| - .or(textField.escapedTextProperty().isEmpty())); | ||
| + DialogPane dialogPane = getDialogPane(); | ||
| + dialogPane.setContent( pane ); | ||
| + dialogPane.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL ); | ||
| - image.bind(Bindings.when(titleField.escapedTextProperty().isNotEmpty()) | ||
| - .then(Bindings.format("", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty())) | ||
| - .otherwise(Bindings.format("", textField.escapedTextProperty(), urlField.escapedTextProperty()))); | ||
| - previewField.textProperty().bind(image); | ||
| + dialogPane.lookupButton( ButtonType.OK ).disableProperty().bind( | ||
| + urlField.escapedTextProperty().isEmpty() | ||
| + .or( textField.escapedTextProperty().isEmpty() ) ); | ||
| - setResultConverter(dialogButton -> { | ||
| - ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null; | ||
| - return (data == ButtonData.OK_DONE) ? image.get() : null; | ||
| - }); | ||
| + image.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() ) | ||
| + .then( Bindings.format( "", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) ) | ||
| + .otherwise( Bindings.format( "", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) ); | ||
| + previewField.textProperty().bind( image ); | ||
| - Platform.runLater(() -> { | ||
| - urlField.requestFocus(); | ||
| + setResultConverter( dialogButton -> { | ||
| + ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null; | ||
| + return (data == ButtonData.OK_DONE) ? image.get() : null; | ||
| + } ); | ||
| - if (urlField.getText().startsWith("http://")) | ||
| - urlField.selectRange("http://".length(), urlField.getLength()); | ||
| - }); | ||
| - } | ||
| + Platform.runLater( () -> { | ||
| + urlField.requestFocus(); | ||
| - private void initComponents() { | ||
| - // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | ||
| - pane = new MigPane(); | ||
| - Label urlLabel = new Label(); | ||
| - urlField = new EscapeTextField(); | ||
| - linkBrowseFileButton = new BrowseFileButton(); | ||
| - Label textLabel = new Label(); | ||
| - textField = new EscapeTextField(); | ||
| - Label titleLabel = new Label(); | ||
| - titleField = new EscapeTextField(); | ||
| - Label previewLabel = new Label(); | ||
| - previewField = new Label(); | ||
| + if( urlField.getText().startsWith( "http://" ) ) { | ||
| + urlField.selectRange( "http://".length(), urlField.getLength() ); | ||
| + } | ||
| + } ); | ||
| + } | ||
| - //======== pane ======== | ||
| - { | ||
| - pane.setCols("[shrink 0,fill][300,grow,fill][fill]"); | ||
| - pane.setRows("[][][][]"); | ||
| + private void initComponents() { | ||
| + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | ||
| + pane = new MigPane(); | ||
| + Label urlLabel = new Label(); | ||
| + urlField = new EscapeTextField(); | ||
| + linkBrowseFileButton = new BrowseFileButton(); | ||
| + Label textLabel = new Label(); | ||
| + textField = new EscapeTextField(); | ||
| + Label titleLabel = new Label(); | ||
| + titleField = new EscapeTextField(); | ||
| + Label previewLabel = new Label(); | ||
| + previewField = new Label(); | ||
| - //---- urlLabel ---- | ||
| - urlLabel.setText(Messages.get("ImageDialog.urlLabel.text")); | ||
| - pane.add(urlLabel, "cell 0 0"); | ||
| + //======== pane ======== | ||
| + { | ||
| + pane.setCols( "[shrink 0,fill][300,grow,fill][fill]" ); | ||
| + pane.setRows( "[][][][]" ); | ||
| - //---- urlField ---- | ||
| - urlField.setEscapeCharacters("()"); | ||
| - urlField.setText("http://yourlink.com"); | ||
| - urlField.setPromptText("http://yourlink.com"); | ||
| - pane.add(urlField, "cell 1 0"); | ||
| - pane.add(linkBrowseFileButton, "cell 2 0"); | ||
| + //---- urlLabel ---- | ||
| + urlLabel.setText( Messages.get( "ImageDialog.urlLabel.text" ) ); | ||
| + pane.add( urlLabel, "cell 0 0" ); | ||
| - //---- textLabel ---- | ||
| - textLabel.setText(Messages.get("ImageDialog.textLabel.text")); | ||
| - pane.add(textLabel, "cell 0 1"); | ||
| + //---- urlField ---- | ||
| + urlField.setEscapeCharacters( "()" ); | ||
| + urlField.setText( "http://yourlink.com" ); | ||
| + urlField.setPromptText( "http://yourlink.com" ); | ||
| + pane.add( urlField, "cell 1 0" ); | ||
| + pane.add( linkBrowseFileButton, "cell 2 0" ); | ||
| - //---- textField ---- | ||
| - textField.setEscapeCharacters("[]"); | ||
| - pane.add(textField, "cell 1 1 2 1"); | ||
| + //---- textLabel ---- | ||
| + textLabel.setText( Messages.get( "ImageDialog.textLabel.text" ) ); | ||
| + pane.add( textLabel, "cell 0 1" ); | ||
| - //---- titleLabel ---- | ||
| - titleLabel.setText(Messages.get("ImageDialog.titleLabel.text")); | ||
| - pane.add(titleLabel, "cell 0 2"); | ||
| - pane.add(titleField, "cell 1 2 2 1"); | ||
| + //---- textField ---- | ||
| + textField.setEscapeCharacters( "[]" ); | ||
| + pane.add( textField, "cell 1 1 2 1" ); | ||
| - //---- previewLabel ---- | ||
| - previewLabel.setText(Messages.get("ImageDialog.previewLabel.text")); | ||
| - pane.add(previewLabel, "cell 0 3"); | ||
| - pane.add(previewField, "cell 1 3 2 1"); | ||
| - } | ||
| - // JFormDesigner - End of component initialization //GEN-END:initComponents | ||
| - } | ||
| + //---- titleLabel ---- | ||
| + titleLabel.setText( Messages.get( "ImageDialog.titleLabel.text" ) ); | ||
| + pane.add( titleLabel, "cell 0 2" ); | ||
| + pane.add( titleField, "cell 1 2 2 1" ); | ||
| - // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | ||
| - private MigPane pane; | ||
| - private EscapeTextField urlField; | ||
| - private BrowseFileButton linkBrowseFileButton; | ||
| - private EscapeTextField textField; | ||
| - private EscapeTextField titleField; | ||
| - private Label previewField; | ||
| + //---- previewLabel ---- | ||
| + previewLabel.setText( Messages.get( "ImageDialog.previewLabel.text" ) ); | ||
| + pane.add( previewLabel, "cell 0 3" ); | ||
| + pane.add( previewField, "cell 1 3 2 1" ); | ||
| + } | ||
| + // JFormDesigner - End of component initialization //GEN-END:initComponents | ||
| + } | ||
| + | ||
| + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | ||
| + private MigPane pane; | ||
| + private EscapeTextField urlField; | ||
| + private BrowseFileButton linkBrowseFileButton; | ||
| + private EscapeTextField textField; | ||
| + private EscapeTextField titleField; | ||
| + private Label previewField; | ||
| // JFormDesigner - End of variables declaration //GEN-END:variables | ||
| } | ||
| import static javafx.scene.input.KeyCode.D; | ||
| import static javafx.scene.input.KeyCode.ENTER; | ||
| -import static javafx.scene.input.KeyCode.W; | ||
| -import static javafx.scene.input.KeyCombination.ALT_DOWN; | ||
| import static javafx.scene.input.KeyCombination.SHORTCUT_DOWN; | ||
| import javafx.scene.input.KeyEvent; | ||
| +import javafx.scene.layout.VBox; | ||
| import org.fxmisc.flowless.VirtualizedScrollPane; | ||
| import org.fxmisc.richtext.StyleClassedTextArea; | ||
| * Markdown editor pane. | ||
| * | ||
| - * Uses pegdown (https://github.com/sirthias/pegdown) for parsing markdown. | ||
| + * Uses pegdown (https://github.com/sirthias/pegdown) for styling the markdown | ||
| + * content within a text area. | ||
| * | ||
| * @author Karl Tauber | ||
| Nodes.addInputMap( textArea, sequence( | ||
| consume( keyPressed( ENTER ), this::enterPressed ), | ||
| - consume( keyPressed( D, SHORTCUT_DOWN ), this::deleteLine ), | ||
| - consume( keyPressed( W, ALT_DOWN ), this::showWhitespace ) | ||
| + consume( keyPressed( D, SHORTCUT_DOWN ), this::deleteLine ) | ||
| ) ); | ||
| textArea.estimatedScrollYProperty().addListener( scrollYListener ); | ||
| textArea.totalHeightEstimateProperty().addListener( scrollYListener ); | ||
| + | ||
| + VBox container = new VBox(); | ||
| + container.getChildren().add( textArea ); | ||
| // create scroll pane | ||
| - scrollPane = new VirtualizedScrollPane<>( textArea ); | ||
| + scrollPane = new VirtualizedScrollPane<>( textArea ); | ||
| overlayGraphicFactory = new ParagraphOverlayGraphicFactory( textArea ); | ||
| return; // editor closed but not yet GCed | ||
| } | ||
| + | ||
| if( e == getOptions().markdownExtensionsProperty() ) { | ||
| // re-process markdown if markdown extensions option changes | ||
| pegDownProcessor = null; | ||
| textChanged( textArea.getText() ); | ||
| } else if( e == getOptions().showWhitespaceProperty() ) { | ||
| updateShowWhitespace(); | ||
| } | ||
| }; | ||
| + | ||
| WeakInvalidationListener weakOptionsListener = new WeakInvalidationListener( optionsListener ); | ||
| getOptions().markdownExtensionsProperty().addListener( weakOptionsListener ); | ||
| applyHighlighting( astRoot ); | ||
| markdownAST.set( astRoot ); | ||
| + } | ||
| + | ||
| + private synchronized PegDownProcessor getPegDownProcessor() { | ||
| + if( this.pegDownProcessor == null ) { | ||
| + this.pegDownProcessor = new PegDownProcessor( getOptions().getMarkdownExtensions() ); | ||
| + } | ||
| + | ||
| + return this.pegDownProcessor; | ||
| } | ||
| private RootNode parseMarkdown( String text ) { | ||
| - if( pegDownProcessor == null ) { | ||
| - pegDownProcessor = new PegDownProcessor( getOptions().getMarkdownExtensions() ); | ||
| - } | ||
| - return pegDownProcessor.parseMarkdown( text.toCharArray() ); | ||
| + return getPegDownProcessor().parseMarkdown( text.toCharArray() ); | ||
| } | ||
| newText = newText.concat( matcher.group( 1 ) ); | ||
| } else { | ||
| - // current line contains only whitespace characters and list markers | ||
| + // current line contains only whitespace characters and list markers | ||
| // --> empty current line | ||
| int caretPosition = textArea.getCaretPosition(); | ||
| public void surroundSelection( String leading, String trailing, String hint ) { | ||
| - // Note: not using textArea.insertText() to insert leading and trailing | ||
| + // Note: not using textArea.insertText() to insert leading and trailing | ||
| // because this would add two changes to undo history | ||
| } | ||
| - // remove leading line separators from leading text | ||
| + // remove leading line separators from leading text | ||
| // if there are line separators before the selected text | ||
| if( leading.startsWith( "\n" ) ) { | ||
| } | ||
| - // remove trailing line separators from trailing or leading text | ||
| + // remove trailing line separators from trailing or leading text | ||
| // if there are line separators after the selected text | ||
| boolean trailingIsEmpty = trailing.isEmpty(); | ||
| -/* | ||
| - * 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.scrivendor.preview; | ||
| - | ||
| -import java.nio.file.Path; | ||
| -import javafx.scene.Node; | ||
| -import javafx.scene.control.ScrollBar; | ||
| -import javafx.scene.control.TextArea; | ||
| -import com.scrivendor.util.Utils; | ||
| -import org.parboiled.support.ToStringFormatter; | ||
| -import org.parboiled.trees.GraphUtils; | ||
| -import org.pegdown.ast.RootNode; | ||
| - | ||
| -/** | ||
| - * Pegdown AST preview. | ||
| - * Prints the AST tree to a text area. | ||
| - * | ||
| - * @author Karl Tauber | ||
| - */ | ||
| -class ASTPreview | ||
| - implements MarkdownPreviewPane.Preview | ||
| -{ | ||
| - private final TextArea textArea = new TextArea(); | ||
| - private ScrollBar vScrollBar; | ||
| - | ||
| - ASTPreview() { | ||
| - textArea.setEditable(false); | ||
| - } | ||
| - | ||
| - Node getNode() { | ||
| - return textArea; | ||
| - } | ||
| - | ||
| - @Override | ||
| - public void update(RootNode astRoot, Path path) { | ||
| - double scrollTop = textArea.getScrollTop(); | ||
| - double scrollLeft = textArea.getScrollLeft(); | ||
| - | ||
| - textArea.setText(GraphUtils.printTree(astRoot, new ToStringFormatter<>())); | ||
| - | ||
| - textArea.setScrollTop(scrollTop); | ||
| - textArea.setScrollLeft(scrollLeft); | ||
| - } | ||
| - | ||
| - @Override | ||
| - public void scrollY(double value) { | ||
| - if (vScrollBar == null) | ||
| - vScrollBar = Utils.findVScrollBar(textArea); | ||
| - if (vScrollBar == null) | ||
| - return; | ||
| - | ||
| - double maxValue = vScrollBar.maxProperty().get(); | ||
| - vScrollBar.setValue(maxValue * value); | ||
| - } | ||
| -} | ||
| -/* | ||
| - * 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.scrivendor.preview; | ||
| - | ||
| -import java.nio.file.Path; | ||
| -import javafx.scene.Node; | ||
| -import javafx.scene.control.ScrollBar; | ||
| -import javafx.scene.control.TextArea; | ||
| -import com.scrivendor.util.Utils; | ||
| -import org.pegdown.ast.RootNode; | ||
| - | ||
| -/** | ||
| - * HTML source preview. | ||
| - * | ||
| - * @author Karl Tauber | ||
| - */ | ||
| -class HtmlSourcePreview | ||
| - implements MarkdownPreviewPane.Preview | ||
| -{ | ||
| - private final TextArea textArea = new TextArea(); | ||
| - private ScrollBar vScrollBar; | ||
| - | ||
| - HtmlSourcePreview() { | ||
| - textArea.setEditable(false); | ||
| - textArea.setWrapText(true); | ||
| - } | ||
| - | ||
| - Node getNode() { | ||
| - return textArea; | ||
| - } | ||
| - | ||
| - @Override | ||
| - public void update(RootNode astRoot, Path path) { | ||
| - double scrollTop = textArea.getScrollTop(); | ||
| - | ||
| - textArea.setText(WebViewPreview.toHtml(astRoot)); | ||
| - | ||
| - textArea.setScrollTop(scrollTop); | ||
| - } | ||
| - | ||
| - @Override | ||
| - public void scrollY(double value) { | ||
| - if (vScrollBar == null) | ||
| - vScrollBar = Utils.findVScrollBar(textArea); | ||
| - if (vScrollBar == null) | ||
| - return; | ||
| - | ||
| - double maxValue = vScrollBar.maxProperty().get(); | ||
| - vScrollBar.setValue(maxValue * value); | ||
| - } | ||
| -} | ||
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| */ | ||
| - | ||
| package com.scrivendor.preview; | ||
| import java.nio.file.Path; | ||
| +import java.util.Collections; | ||
| import javafx.application.Platform; | ||
| import javafx.beans.property.DoubleProperty; | ||
| import javafx.beans.property.ObjectProperty; | ||
| import javafx.beans.property.SimpleDoubleProperty; | ||
| import javafx.beans.property.SimpleObjectProperty; | ||
| -import javafx.geometry.Side; | ||
| import javafx.scene.Node; | ||
| -import javafx.scene.control.Tab; | ||
| -import javafx.scene.control.TabPane; | ||
| -import javafx.scene.control.TabPane.TabClosingPolicy; | ||
| -import com.scrivendor.Messages; | ||
| +import javafx.scene.control.ScrollPane; | ||
| +import static javafx.scene.control.ScrollPane.ScrollBarPolicy.ALWAYS; | ||
| +import javafx.scene.web.WebEngine; | ||
| +import javafx.scene.web.WebView; | ||
| +import org.pegdown.LinkRenderer; | ||
| +import org.pegdown.ToHtmlSerializer; | ||
| +import org.pegdown.VerbatimSerializer; | ||
| import org.pegdown.ast.RootNode; | ||
| +import org.pegdown.plugins.PegDownPlugins; | ||
| /** | ||
| * Markdown preview pane. | ||
| - * | ||
| - * Uses pegdown AST. | ||
| * | ||
| * @author Karl Tauber | ||
| */ | ||
| -public class MarkdownPreviewPane | ||
| -{ | ||
| - private final TabPane tabPane = new TabPane(); | ||
| - private final WebViewPreview webViewPreview = new WebViewPreview(); | ||
| - private final HtmlSourcePreview htmlSourcePreview = new HtmlSourcePreview(); | ||
| - private final ASTPreview astPreview = new ASTPreview(); | ||
| +public class MarkdownPreviewPane extends ScrollPane { | ||
| - interface Preview { | ||
| - void update(RootNode astRoot, Path path); | ||
| - void scrollY(double value); | ||
| - } | ||
| + private final ObjectProperty<RootNode> markdownAST = new SimpleObjectProperty<>(); | ||
| + private final ObjectProperty<Path> path = new SimpleObjectProperty<>(); | ||
| + private final DoubleProperty scrollY = new SimpleDoubleProperty(); | ||
| - public MarkdownPreviewPane() { | ||
| - tabPane.setSide(Side.BOTTOM); | ||
| - tabPane.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE); | ||
| + private final WebView webView = new WebView(); | ||
| + private int lastScrollX; | ||
| + private int lastScrollY; | ||
| - Tab webViewTab = new Tab(Messages.get("MarkdownPreviewPane.webViewTab"), webViewPreview.getNode()); | ||
| - webViewTab.setUserData(webViewPreview); | ||
| - tabPane.getTabs().add(webViewTab); | ||
| + private boolean delayScroll; | ||
| - Tab htmlSourceTab = new Tab(Messages.get("MarkdownPreviewPane.htmlSourceTab"), htmlSourcePreview.getNode()); | ||
| - htmlSourceTab.setUserData(htmlSourcePreview); | ||
| - tabPane.getTabs().add(htmlSourceTab); | ||
| + public MarkdownPreviewPane() { | ||
| + setVbarPolicy( ALWAYS ); | ||
| - Tab astTab = new Tab(Messages.get("MarkdownPreviewPane.astTab"), astPreview.getNode()); | ||
| - astTab.setUserData(astPreview); | ||
| - tabPane.getTabs().add(astTab); | ||
| + markdownASTProperty().addListener( (observable, oldValue, newValue) -> { | ||
| + update(); | ||
| + } ); | ||
| - tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldTab, newTab) -> { | ||
| - update(); | ||
| - scrollY(); | ||
| - }); | ||
| + pathProperty().addListener( (observable, oldValue, newValue) -> { | ||
| + update(); | ||
| + } ); | ||
| - path.addListener((observable, oldValue, newValue) -> { | ||
| - update(); | ||
| - }); | ||
| + scrollYProperty().addListener( (observable, oldValue, newValue) -> { | ||
| + scrollY(); | ||
| + } ); | ||
| + } | ||
| - markdownAST.addListener((observable, oldValue, newValue) -> { | ||
| - update(); | ||
| - }); | ||
| + private String toHtml() { | ||
| + final RootNode root = getMarkdownAST(); | ||
| - scrollY.addListener((observable, oldValue, newValue) -> { | ||
| - scrollY(); | ||
| - }); | ||
| - } | ||
| + return root == null | ||
| + ? "" | ||
| + : new ToHtmlSerializer( new LinkRenderer(), | ||
| + Collections.<String, VerbatimSerializer>emptyMap(), | ||
| + PegDownPlugins.NONE.getHtmlSerializerPlugins() ).toHtml( root ); | ||
| + } | ||
| - public Node getNode() { | ||
| - return tabPane; | ||
| - } | ||
| + public void update() { | ||
| + if( !getEngine().getLoadWorker().isRunning() ) { | ||
| + setScrollXY(); | ||
| + } | ||
| - private Preview getActivePreview() { | ||
| - return (Preview) tabPane.getSelectionModel().getSelectedItem().getUserData(); | ||
| - } | ||
| + getEngine().loadContent( | ||
| + "<!DOCTYPE html>" | ||
| + + "<html>" | ||
| + + "<head>" | ||
| + + "<link rel='stylesheet' href='" + getClass().getResource( "markdownpad-github.css" ) + "'>" | ||
| + + getBase() | ||
| + + "</head>" | ||
| + + "<body" + getScrollScript() + ">" | ||
| + + toHtml() | ||
| + + "</body>" | ||
| + + "</html>" ); | ||
| + } | ||
| - private void update() { | ||
| - getActivePreview().update(getMarkdownAST(), getPath()); | ||
| - } | ||
| + /** | ||
| + * Obtain the window.scrollX and window.scrollY from web engine, but only no | ||
| + * worker is running (in this case the result would be zero). | ||
| + */ | ||
| + private void setScrollXY() { | ||
| + lastScrollX = getNumber( execute( "window.scrollX" ) ); | ||
| + lastScrollY = getNumber( execute( "window.scrollY" ) ); | ||
| + } | ||
| - private boolean scrollYrunLaterPending; | ||
| - private void scrollY() { | ||
| - // avoid too many (and useless) runLater() invocations | ||
| - if (scrollYrunLaterPending) | ||
| - return; | ||
| - scrollYrunLaterPending = true; | ||
| + private int getNumber( final Object number ) { | ||
| + return (number instanceof Number) ? ((Number)number).intValue() : 0; | ||
| + } | ||
| - Platform.runLater(() -> { | ||
| - scrollYrunLaterPending = false; | ||
| - getActivePreview().scrollY(getScrollY()); | ||
| - }); | ||
| - } | ||
| + private String getBase() { | ||
| + final Path path = getPath(); | ||
| - // 'path' property | ||
| - private final ObjectProperty<Path> path = new SimpleObjectProperty<>(); | ||
| - public Path getPath() { return path.get(); } | ||
| - public void setPath(Path path) { this.path.set(path); } | ||
| - public ObjectProperty<Path> pathProperty() { return path; } | ||
| + return path == null | ||
| + ? "" | ||
| + : ("<base href='" + path.getParent().toUri().toString() + "'>"); | ||
| + } | ||
| - // 'markdownAST' property | ||
| - private final ObjectProperty<RootNode> markdownAST = new SimpleObjectProperty<RootNode>(); | ||
| - public RootNode getMarkdownAST() { return markdownAST.get(); } | ||
| - public void setMarkdownAST(RootNode astRoot) { markdownAST.set(astRoot); } | ||
| - public ObjectProperty<RootNode> markdownASTProperty() { return markdownAST; } | ||
| + private String getScrollScript() { | ||
| + return (lastScrollX > 0 || lastScrollY > 0) | ||
| + ? (" onload='window.scrollTo(" + lastScrollX + "," + lastScrollY + ");'") | ||
| + : ""; | ||
| + } | ||
| - // 'scrollY' property | ||
| - private final DoubleProperty scrollY = new SimpleDoubleProperty(); | ||
| - public double getScrollY() { return scrollY.get(); } | ||
| - public void setScrollY(double value) { scrollY.set(value); } | ||
| - public DoubleProperty scrollYProperty() { return scrollY; } | ||
| + /** | ||
| + * Helps avoid many superfluous runLater() calls. | ||
| + */ | ||
| + private void scrollY() { | ||
| + if( !delayScroll ) { | ||
| + delayScroll = true; | ||
| + | ||
| + Platform.runLater( () -> { | ||
| + delayScroll = false; | ||
| + scrollY( getScrollY() ); | ||
| + } ); | ||
| + } | ||
| + } | ||
| + | ||
| + private void scrollY( double value ) { | ||
| + execute( | ||
| + "window.scrollTo(0, (document.body.scrollHeight - window.innerHeight) * " | ||
| + + value | ||
| + + ");" ); | ||
| + } | ||
| + | ||
| + public Path getPath() { | ||
| + return pathProperty().get(); | ||
| + } | ||
| + | ||
| + public void setPath( Path path ) { | ||
| + pathProperty().set( path ); | ||
| + } | ||
| + | ||
| + public ObjectProperty<Path> pathProperty() { | ||
| + return this.path; | ||
| + } | ||
| + | ||
| + public RootNode getMarkdownAST() { | ||
| + return markdownASTProperty().get(); | ||
| + } | ||
| + | ||
| + public void setMarkdownAST( RootNode astRoot ) { | ||
| + markdownASTProperty().set( astRoot ); | ||
| + } | ||
| + | ||
| + public ObjectProperty<RootNode> markdownASTProperty() { | ||
| + return this.markdownAST; | ||
| + } | ||
| + | ||
| + public double getScrollY() { | ||
| + return scrollYProperty().get(); | ||
| + } | ||
| + | ||
| + public void setScrollY( double value ) { | ||
| + scrollYProperty().set( value ); | ||
| + } | ||
| + | ||
| + public DoubleProperty scrollYProperty() { | ||
| + return this.scrollY; | ||
| + } | ||
| + | ||
| + public Node getNode() { | ||
| + return getWebView(); | ||
| + } | ||
| + | ||
| + private Object execute( String script ) { | ||
| + return getEngine().executeScript( script ); | ||
| + } | ||
| + | ||
| + private WebEngine getEngine() { | ||
| + return getWebView().getEngine(); | ||
| + } | ||
| + | ||
| + private WebView getWebView() { | ||
| + return this.webView; | ||
| + } | ||
| } | ||
| -/* | ||
| - * 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.scrivendor.preview; | ||
| - | ||
| -import java.nio.file.Path; | ||
| -import java.util.Collections; | ||
| -import javafx.scene.Node; | ||
| -import javafx.scene.web.WebView; | ||
| -import org.pegdown.LinkRenderer; | ||
| -import org.pegdown.ToHtmlSerializer; | ||
| -import org.pegdown.VerbatimSerializer; | ||
| -import org.pegdown.ast.RootNode; | ||
| -import org.pegdown.plugins.PegDownPlugins; | ||
| - | ||
| -/** | ||
| - * WebView preview. | ||
| - * Serializes the AST tree to HTML and shows it in a WebView. | ||
| - * | ||
| - * @author Karl Tauber | ||
| - */ | ||
| -class WebViewPreview | ||
| - implements MarkdownPreviewPane.Preview | ||
| -{ | ||
| - private final WebView webView = new WebView(); | ||
| - private int lastScrollX; | ||
| - private int lastScrollY; | ||
| - | ||
| - Node getNode() { | ||
| - return webView; | ||
| - } | ||
| - | ||
| - static String toHtml(RootNode astRoot) { | ||
| - if (astRoot == null) | ||
| - return ""; | ||
| - return new ToHtmlSerializer(new LinkRenderer(), | ||
| - Collections.<String, VerbatimSerializer>emptyMap(), | ||
| - PegDownPlugins.NONE.getHtmlSerializerPlugins()) | ||
| - .toHtml(astRoot); | ||
| - } | ||
| - | ||
| - @Override | ||
| - public void update(RootNode astRoot, Path path) { | ||
| - if (!webView.getEngine().getLoadWorker().isRunning()) { | ||
| - // get window.scrollX and window.scrollY from web engine, | ||
| - // but only no worker is running (in this case the result would be zero) | ||
| - Object scrollXobj = webView.getEngine().executeScript("window.scrollX"); | ||
| - Object scrollYobj = webView.getEngine().executeScript("window.scrollY"); | ||
| - lastScrollX = (scrollXobj instanceof Number) ? ((Number)scrollXobj).intValue() : 0; | ||
| - lastScrollY = (scrollYobj instanceof Number) ? ((Number)scrollYobj).intValue() : 0; | ||
| - } | ||
| - | ||
| - String base = (path != null) | ||
| - ? ("<base href=\"" + path.getParent().toUri().toString() + "\">\n") | ||
| - : ""; | ||
| - String scrollScript = (lastScrollX > 0 || lastScrollY > 0) | ||
| - ? (" onload='window.scrollTo("+lastScrollX+", "+lastScrollY+");'") | ||
| - : ""; | ||
| - | ||
| - webView.getEngine().loadContent( | ||
| - "<!DOCTYPE html>\n" | ||
| - + "<html>\n" | ||
| - + "<head>\n" | ||
| - + "<link rel=\"stylesheet\" href=\"" + getClass().getResource("markdownpad-github.css") + "\">\n" | ||
| - + base | ||
| - + "</head>\n" | ||
| - + "<body" + scrollScript + ">\n" | ||
| - + toHtml(astRoot) | ||
| - + "</body>\n" | ||
| - + "</html>"); | ||
| - } | ||
| - | ||
| - @Override | ||
| - public void scrollY(double value) { | ||
| - webView.getEngine().executeScript( | ||
| - "window.scrollTo(0, (document.body.scrollHeight - window.innerHeight) * "+value+");"); | ||
| - } | ||
| -} | ||
| /* | ||
| - * Copyright (c) 2016 White Magic Software, Inc. | ||
| + * Copyright 2016 White Magic Software, Inc. | ||
| * | ||
| * All rights reserved. |
| +/* | ||
| + * Copyright 2016 White Magic Software, Inc. | ||
| + * | ||
| + * 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.scrivendor.service; | ||
| + | ||
| +/** | ||
| + * | ||
| + * @author White Magic Software, Ltd. | ||
| + */ | ||
| +public interface Pipeline extends Service { | ||
| + | ||
| + public String process( String content ); | ||
| +} | ||
| .markdown-editor { | ||
| - -fx-font-size: 14px; | ||
| + -fx-font-size: 14px; | ||
| } | ||
| - | ||
| /*---- headers ----*/ | ||
| -.markdown-editor .h1 { -fx-font-size: 2em; } | ||
| +.markdown-editor .h1 { -fx-font-size: 2.25em; } | ||
| .markdown-editor .h2 { -fx-font-size: 1.75em; } | ||
| .markdown-editor .h3 { -fx-font-size: 1.5em; } | ||
| .markdown-editor .h5, | ||
| .markdown-editor .h6 { | ||
| - -fx-font-weight: bold; | ||
| - -fx-fill: derive(crimson, -20%); | ||
| + -fx-font-weight: bold; | ||
| + -fx-fill: derive(crimson, -20%); | ||
| } | ||
| /*---- inlines ----*/ | ||
| .markdown-editor .strong { | ||
| - -fx-font-weight: bold; | ||
| + -fx-font-weight: bold; | ||
| } | ||
| .markdown-editor .em { | ||
| - -fx-font-style: italic; | ||
| + -fx-font-style: italic; | ||
| } | ||
| .markdown-editor .del { | ||
| - -fx-strikethrough: true; | ||
| + -fx-strikethrough: true; | ||
| } | ||
| .markdown-editor .a { | ||
| - -fx-fill: #4183C4 !important; | ||
| + -fx-fill: #4183C4 !important; | ||
| } | ||
| .markdown-editor .img { | ||
| - -fx-fill: #4183C4 !important; | ||
| + -fx-fill: #4183C4 !important; | ||
| } | ||
| .markdown-editor .code { | ||
| - -fx-font-family: monospace; | ||
| - -fx-fill: #090 !important; | ||
| + -fx-font-family: monospace; | ||
| + -fx-fill: #090 !important; | ||
| } | ||
| /*---- blocks ----*/ | ||
| .markdown-editor .pre { | ||
| - -fx-font-family: monospace; | ||
| - -fx-fill: #060 !important; | ||
| + -fx-font-family: monospace; | ||
| + -fx-fill: #060 !important; | ||
| } | ||
| .markdown-editor .blockquote { | ||
| - -fx-fill: #777; | ||
| + -fx-fill: #777; | ||
| } | ||
| .markdown-editor .li { | ||
| - -fx-fill: #444; | ||
| + -fx-fill: #444; | ||
| } | ||
| .markdown-editor .dl { | ||
| } | ||
| .markdown-editor .dt { | ||
| - -fx-font-weight: bold; | ||
| - -fx-font-style: italic; | ||
| + -fx-font-weight: bold; | ||
| + -fx-font-style: italic; | ||
| } | ||
| .markdown-editor .dd { | ||
| - -fx-fill: #444; | ||
| + -fx-fill: #444; | ||
| } | ||
| /*---- table ----*/ | ||
| .markdown-editor .table { | ||
| - -fx-font-family: monospace; | ||
| + -fx-font-family: monospace; | ||
| } | ||
| .markdown-editor .th { | ||
| - -fx-font-weight: bold; | ||
| + -fx-font-weight: bold; | ||
| } | ||
| .markdown-editor .html { | ||
| - -fx-font-family: monospace; | ||
| - -fx-fill: derive(crimson, -50%); | ||
| + -fx-font-family: monospace; | ||
| + -fx-fill: derive(crimson, -50%); | ||
| } | ||
| .markdown-editor .monospace { | ||
| - -fx-font-family: monospace; | ||
| + -fx-font-family: monospace; | ||
| } | ||
| Delta | 342 lines added, 484 lines removed, 142-line decrease |
|---|