Dave Jarvis' Repositories

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

Disable non-functional unit test, add module extensions to unit tests

Authordjarvis <email>
Date2021-05-02 22:06:19 GMT-0700
Commit0ccf4d5aa0e5bdfc658dfa0d8a20915566ed97ec
Parentc530464
build.gradle
javafx {
- version = "15"
+ version = "16"
modules = ['javafx.controls', 'javafx.swing']
configuration = 'compileOnly'
implementation 'org.fxmisc.wellbehaved:wellbehavedfx:0.3.3'
implementation 'com.miglayout:miglayout-javafx:5.2'
- implementation('com.dlsc.preferencesfx:preferencesfx-core:11.7.0') {
- exclude group: 'org.openjfx'
- }
+ implementation 'com.dlsc.preferencesfx:preferencesfx-core:11.8.0'
// Pure JavaFX File Chooser
implementation 'com.ximpleware:vtd-xml:2.13.4'
implementation 'net.sf.saxon:Saxon-HE:10.3'
- //implementation 'xalan:xalan:2.7.2'
// HTML parsing and rendering
applicationDefaultJvmArgs = [
- "--add-opens=javafx.controls/javafx.scene.control=ALL-UNNAMED",
- "--add-opens=javafx.controls/javafx.scene.control.skin=ALL-UNNAMED",
- "--add-opens=javafx.graphics/com.sun.javafx.css=ALL-UNNAMED",
- "--add-exports=javafx.graphics/com.sun.javafx.application=ALL-UNNAMED",
+ "--add-opens=javafx.controls/javafx.scene.control=ALL-UNNAMED",
+ "--add-opens=javafx.controls/javafx.scene.control.skin=ALL-UNNAMED",
+ "--add-opens=javafx.graphics/javafx.scene.text=ALL-UNNAMED",
+ "--add-opens=javafx.graphics/com.sun.javafx.css=ALL-UNNAMED",
+ "--add-opens=javafx.graphics/com.sun.javafx.text=ALL-UNNAMED",
+ "--add-exports=javafx.graphics/com.sun.javafx.application=ALL-UNNAMED",
+ "--add-exports=javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED",
+ "--add-exports=javafx.graphics/com.sun.javafx.scene.traversal=ALL-UNNAMED",
+ "--add-exports=javafx.graphics/com.sun.javafx.scene.traversal=ALL-UNNAMED",
+ "--add-exports=javafx.graphics/com.sun.javafx.text=ALL-UNNAMED",
]
}
doFirst {
jvmArgs = [
- '--add-exports', "javafx.base/com.sun.javafx.event=ALL-UNNAMED",
+ "--add-opens=javafx.controls/javafx.scene.control=ALL-UNNAMED",
+ "--add-opens=javafx.controls/javafx.scene.control.skin=ALL-UNNAMED",
+ "--add-opens=javafx.graphics/javafx.scene.text=ALL-UNNAMED",
+ "--add-opens=javafx.graphics/com.sun.javafx.css=ALL-UNNAMED",
+ "--add-opens=javafx.graphics/com.sun.javafx.text=ALL-UNNAMED",
+ "--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED",
+ "--add-exports=javafx.graphics/com.sun.javafx.application=ALL-UNNAMED",
+ "--add-exports=javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED",
+ "--add-exports=javafx.graphics/com.sun.javafx.scene.traversal=ALL-UNNAMED",
+ "--add-exports=javafx.graphics/com.sun.javafx.scene.traversal=ALL-UNNAMED",
+ "--add-exports=javafx.graphics/com.sun.javafx.text=ALL-UNNAMED",
]
- //classpath = files()
}
installer.sh
readonly FILE_APP_JAR="${APP_NAME}.jar"
+# JDK 16 work-around until RichTextFX is fixed.
+# See: https://github.com/FXMisc/RichTextFX/issues/1013
+readonly OPT_JAVA="--illegal-access=permit"
+
ARG_JAVA_OS="linux"
ARG_JAVA_ARCH="amd64"
readonly SCRIPT_SRC="\$(dirname "\${BASH_SOURCE[\${#BASH_SOURCE[@]} - 1]}")"
-"\${SCRIPT_SRC}/${ARG_JAVA_DIR}/bin/java" -jar "\${SCRIPT_SRC}/${FILE_APP_JAR}" "\$@" 2>&1 >/dev/null &
+"\${SCRIPT_SRC}/${ARG_JAVA_DIR}/bin/java" ${OPT_JAVA} -jar "\${SCRIPT_SRC}/${FILE_APP_JAR}" "\$@" 2>&1 >/dev/null &
__EOT
set SCRIPT_DIR=%~dp0
-"%SCRIPT_DIR%\\${ARG_JAVA_DIR}\\bin\\java" -jar "%SCRIPT_DIR%\\${APP_NAME}.jar" %*
+"%SCRIPT_DIR%\\${ARG_JAVA_DIR}\\bin\\java" ${OPT_JAVA} -jar "%SCRIPT_DIR%\\${APP_NAME}.jar" %*
__EOT
src/main/java/com/keenwrite/MainApp.java
*/
public static void main( final String[] args ) {
- disableLogging();
+ //disableLogging();
launch( args );
}
src/main/java/com/keenwrite/io/MediaTypeSniffer.java
FORMAT.put( ints( 0x25, 0x21, 0x50, 0x53 ), APP_PS );
FORMAT.put( ints( 0x38, 0x42, 0x50, 0x53, 0x00, 0x01 ), IMAGE_PHOTOSHOP );
- FORMAT.put( ints( 0x8A, 0x4D, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), VIDEO_MNG );
+ FORMAT.put( ints( 0x8A, 0x4D, 0x4E, 0x47, 0x0A, 0x1A, 0x0A ), VIDEO_MNG );
FORMAT.put( ints( 0x42, 0x4D ), IMAGE_BMP );
FORMAT.put( ints( 0xFF, 0xFB, 0x30 ), AUDIO_MP3 );
src/main/java/com/keenwrite/preview/HtmlPreview.java
import static com.keenwrite.ui.fonts.IconFactory.getIconFont;
import static java.awt.BorderLayout.*;
-import static java.lang.Math.max;
-import static java.lang.String.format;
-import static java.lang.Thread.sleep;
-import static javafx.scene.CacheHint.SPEED;
-import static javax.swing.SwingUtilities.invokeLater;
-import static org.controlsfx.glyphfont.FontAwesome.Glyph.LOCK;
-import static org.controlsfx.glyphfont.FontAwesome.Glyph.UNLOCK_ALT;
-
-/**
- * Responsible for parsing an HTML document.
- */
-public final class HtmlPreview extends SwingNode {
-
- /**
- * The order is important: Swing factory will replace SVG images with
- * a blank image, which will cause the chained factory to cache the image
- * and exit. Instead, the SVG must execute first to rasterize the content.
- * Consequently, the chained factory must maintain insertion order.
- */
- private static final ChainedReplacedElementFactory FACTORY
- = new ChainedReplacedElementFactory(
- new SvgReplacedElementFactory(),
- new SwingReplacedElementFactory()
- );
-
- /**
- * Used to populate the {@link #HTML_HEAD} with stylesheet file references.
- */
- private static final String HTML_STYLESHEET =
- "<link rel='stylesheet' href='%s'>";
-
- private static final String HTML_BASE =
- "<base href='%s'>";
-
- /**
- * Render CSS using points (pt) not pixels (px) to reduce the chance of
- * poor rendering. The {@link #generateHead()} method fills placeholders.
- * When the user has not set a locale, only one stylesheet is added to
- * the document. In order, the placeholders are as follows:
- * <ol>
- * <li>%s --- language</li>
- * <li>%s --- default stylesheet</li>
- * <li>%s --- language-specific stylesheet</li>
- * <li>%s --- font family</li>
- * <li>%d --- font size (must be pixels, not points due to bug)</li>
- * <li>%s --- base href</li>
- * </p>
- */
- private static final String HTML_HEAD =
- """
- <!doctype html>
- <html lang='%s'><head><title> </title><meta charset='utf-8'>
- %s%s<style>body{font-family:'%s';font-size: %dpx;}</style>%s</head><body>
- """;
-
- private static final String HTML_TAIL = "</body></html>";
-
- private static final URL HTML_STYLE_PREVIEW = toUrl( STYLESHEET_PREVIEW );
-
- /**
- * Reusing this buffer prevents repetitious memory re-allocations.
- */
- private final StringBuilder mDocument = new StringBuilder( 65536 );
-
- private HtmlPanel mView;
- private JScrollPane mScrollPane;
- private String mBaseUriPath = "";
- private String mHead = "";
-
- private volatile boolean mLocked;
- private final JButton mScrollLockButton = new JButton();
-
- private final Workspace mWorkspace;
-
- /**
- * Creates a new preview pane that can scroll to the caret position within the
- * document.
- *
- * @param workspace Contains locale and font size information.
- */
- public HtmlPreview( final Workspace workspace ) {
- mWorkspace = workspace;
-
- // Attempts to prevent a flash of black un-styled content upon load.
- setStyle( "-fx-background-color: white;" );
-
- invokeLater( () -> {
- mHead = generateHead();
- mView = new HtmlPanel();
- mScrollPane = new JScrollPane( mView );
- final var verticalBar = mScrollPane.getVerticalScrollBar();
- final var verticalPanel = new JPanel( new BorderLayout() );
-
- mScrollLockButton.setFont( getIconFont( 14 ) );
- mScrollLockButton.setText( getLockText( mLocked ) );
- mScrollLockButton.setMargin( new Insets( 1, 0, 0, 0 ) );
- mScrollLockButton.addActionListener( e -> fireScrollLockEvent( !mLocked ) );
-
- verticalPanel.add( verticalBar, CENTER );
- verticalPanel.add( mScrollLockButton, PAGE_END );
-
- final var wrapper = new JPanel( new BorderLayout() );
- wrapper.add( mScrollPane, CENTER );
- wrapper.add( verticalPanel, LINE_END );
-
- // Enabling the cache attempts to prevent black flashes when resizing.
- setCache( true );
- setCacheHint( SPEED );
- setContent( wrapper );
-
- final var context = mView.getSharedContext();
- final var textRenderer = context.getTextRenderer();
- context.setReplacedElementFactory( FACTORY );
- textRenderer.setSmoothingThreshold( 0 );
-
- localeProperty().addListener( ( c, o, n ) -> rerender() );
- fontFamilyProperty().addListener( ( c, o, n ) -> rerender() );
- fontSizeProperty().addListener( ( c, o, n ) -> rerender() );
- } );
-
- register( this );
- }
-
- @Subscribe
- public void handle( final ScrollLockEvent event ) {
- mLocked = event.isLocked();
- invokeLater( () -> mScrollLockButton.setText( getLockText( mLocked ) ) );
- }
-
- /**
- * Updates the internal HTML source shown in the preview pane.
- *
- * @param html The new HTML document to display.
- */
- public void render( final String html ) {
- mView.render( decorate( html ), getBaseUri() );
- }
-
- /**
- * Clears the caches then re-renders the content.
- */
- public void refresh() {
- FACTORY.clearCache();
- rerender();
- }
-
- /**
- * Recomputes the HTML head then renders the document.
- */
- private void rerender() {
- mHead = generateHead();
- render( mDocument.toString() );
- }
-
- /**
- * Attaches the HTML head prefix and HTML tail suffix to the given HTML
- * string.
- *
- * @param html The HTML to adorn with opening and closing tags.
- * @return A complete HTML document, ready for rendering.
- */
- private String decorate( final String html ) {
- mDocument.setLength( 0 );
- mDocument.append( html );
-
- // Head and tail must be separate from document due to re-rendering.
- return mHead + mDocument.toString() + HTML_TAIL;
- }
-
- /**
- * Called when settings are changed that affect the HTML document preamble.
- * This is a minor performance optimization to avoid generating the head
- * each time that the document itself changes.
- *
- * @return A new doctype and HTML {@code head} element.
- */
- private String generateHead() {
- final var locale = getLocale();
- final var url = toUrl( locale );
- final var base = getBaseUri();
-
- // Point sizes are converted to pixels because of a rendering bug.
- return format(
- HTML_HEAD,
- locale.getLanguage(),
- format( HTML_STYLESHEET, HTML_STYLE_PREVIEW ),
- url == null ? "" : format( HTML_STYLESHEET, url ),
- getFontFamily(),
- toPixels( getFontSize() ),
- base.isBlank() ? "" : format( HTML_BASE, base )
- );
- }
-
- /**
- * Clears the preview pane by rendering an empty string.
- */
- public void clear() {
- render( "" );
- }
-
- /**
- * Sets the base URI to the containing directory the file being edited.
- *
- * @param path The path to the file being edited.
- */
- public void setBaseUri( final Path path ) {
- final var parent = path.getParent();
- mBaseUriPath = parent == null ? "" : parent.toUri().toString();
- }
-
- /**
- * Scrolls to the closest element matching the given identifier without
- * waiting for the document to be ready.
- *
- * @param id Scroll the preview pane to this unique paragraph identifier.
- */
- public void scrollTo( final String id ) {
- if( mLocked ) {
- return;
- }
-
- invokeLater( () -> {
- int iter = 0;
- Box box = null;
-
- while( iter++ < 3 && ((box = mView.getBoxById( id )) == null) ) {
- try {
- sleep( 10 );
- } catch( final Exception ex ) {
- clue( ex );
- }
- }
-
- scrollTo( box );
- } );
- }
-
- /**
- * Scrolls to the location specified by the {@link Box} that corresponds
- * to a point somewhere in the preview pane. If there is no caret, then
- * this will not change the scroll position. Changing the scroll position
- * to the top if the {@link Box} instance is {@code null} will result in
- * jumping around a lot and inconsistent synchronization issues.
- *
- * @param box The rectangular region containing the caret, or {@code null}
- * if the HTML does not have a caret.
- */
- private void scrollTo( final Box box ) {
- if( box != null ) {
- invokeLater( () -> {
- mView.scrollTo( createPoint( box ) );
- getScrollPane().repaint();
- } );
- }
- }
-
- /**
- * Creates a {@link Point} to use as a reference for scrolling to the area
- * described by the given {@link Box}. The {@link Box} coordinates are used
- * to populate the {@link Point}'s location, with minor adjustments for
- * vertical centering.
- *
- * @param box The {@link Box} that represents a scrolling anchor reference.
- * @return A coordinate suitable for scrolling to.
- */
- private Point createPoint( final Box box ) {
- assert box != null;
-
- // Scroll back up by half the height of the scroll bar to keep the typing
- // area within the view port. Otherwise the view port will have jumped too
- // high up and the most recently typed letters won't be visible.
- int y = max( box.getAbsY() - getVerticalScrollBarHeight() / 2, 0 );
- int x = box.getAbsX();
-
- if( !box.getStyle().isInline() ) {
- final var margin = box.getMargin( mView.getLayoutContext() );
- y += margin.top();
- x += margin.left();
- }
-
- return new Point( x, y );
- }
-
- private String getBaseUri() {
- return mBaseUriPath;
- }
-
- private JScrollPane getScrollPane() {
- return mScrollPane;
- }
-
- public JScrollBar getVerticalScrollBar() {
- return getScrollPane().getVerticalScrollBar();
- }
-
- private int getVerticalScrollBarHeight() {
- return getVerticalScrollBar().getHeight();
- }
-
- /**
- * Returns the ISO 639 alpha-2 or alpha-3 language code followed by a hyphen
- * followed by the ISO 15924 alpha-4 script code, followed by an ISO 3166
- * alpha-2 country code or UN M.49 numeric-3 area code. For example, this
- * could return "en-Latn-CA" for Canadian English written in the Latin
- * character set.
- *
- * @return Unique identifier for language and country.
- */
- private static URL toUrl( final Locale locale ) {
- return toUrl(
- get(
- sSettings.getSetting( STYLESHEET_PREVIEW_LOCALE, "" ),
- locale.getLanguage(),
- locale.getScript(),
- locale.getCountry()
- )
- );
- }
-
- private static URL toUrl( final String path ) {
- return HtmlPreview.class.getResource( path );
- }
-
- private Locale getLocale() {
- return localeProperty().toLocale();
- }
-
- private LocaleProperty localeProperty() {
- return mWorkspace.localeProperty( KEY_LANGUAGE_LOCALE );
- }
-
- private String getFontFamily() {
- return fontFamilyProperty().get();
- }
-
- private StringProperty fontFamilyProperty() {
- return mWorkspace.stringProperty( KEY_UI_FONT_PREVIEW_NAME );
- }
-
- private double getFontSize() {
- return fontSizeProperty().get();
- }
-
- /**
- * Returns the font size in points.
- *
- * @return The user-defined font size (in pt).
- */
- private DoubleProperty fontSizeProperty() {
- return mWorkspace.doubleProperty( KEY_UI_FONT_PREVIEW_SIZE );
- }
-
- private String getLockText( final boolean locked ) {
- return Character.toString( (locked ? LOCK : UNLOCK_ALT).getChar() );
+import static java.awt.event.KeyEvent.*;
+import static java.lang.Math.max;
+import static java.lang.String.format;
+import static java.lang.Thread.sleep;
+import static javafx.scene.CacheHint.SPEED;
+import static javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static javax.swing.SwingUtilities.invokeLater;
+import static org.controlsfx.glyphfont.FontAwesome.Glyph.LOCK;
+import static org.controlsfx.glyphfont.FontAwesome.Glyph.UNLOCK_ALT;
+
+/**
+ * Responsible for parsing an HTML document.
+ */
+public final class HtmlPreview extends SwingNode {
+
+ /**
+ * The order is important: Swing factory will replace SVG images with
+ * a blank image, which will cause the chained factory to cache the image
+ * and exit. Instead, the SVG must execute first to rasterize the content.
+ * Consequently, the chained factory must maintain insertion order.
+ */
+ private static final ChainedReplacedElementFactory FACTORY
+ = new ChainedReplacedElementFactory(
+ new SvgReplacedElementFactory(),
+ new SwingReplacedElementFactory()
+ );
+
+ /**
+ * Used to populate the {@link #HTML_HEAD} with stylesheet file references.
+ */
+ private static final String HTML_STYLESHEET =
+ "<link rel='stylesheet' href='%s'>";
+
+ private static final String HTML_BASE =
+ "<base href='%s'>";
+
+ /**
+ * Render CSS using points (pt) not pixels (px) to reduce the chance of
+ * poor rendering. The {@link #generateHead()} method fills placeholders.
+ * When the user has not set a locale, only one stylesheet is added to
+ * the document. In order, the placeholders are as follows:
+ * <ol>
+ * <li>%s --- language</li>
+ * <li>%s --- default stylesheet</li>
+ * <li>%s --- language-specific stylesheet</li>
+ * <li>%s --- font family</li>
+ * <li>%d --- font size (must be pixels, not points due to bug)</li>
+ * <li>%s --- base href</li>
+ * </p>
+ */
+ private static final String HTML_HEAD =
+ """
+ <!doctype html>
+ <html lang='%s'><head><title> </title><meta charset='utf-8'>
+ %s%s<style>body{font-family:'%s';font-size: %dpx;}</style>%s</head><body>
+ """;
+
+ private static final String HTML_TAIL = "</body></html>";
+
+ private static final URL HTML_STYLE_PREVIEW = toUrl( STYLESHEET_PREVIEW );
+
+ /**
+ * Reusing this buffer prevents repetitious memory re-allocations.
+ */
+ private final StringBuilder mDocument = new StringBuilder( 65536 );
+
+ private HtmlPanel mView;
+ private JScrollPane mScrollPane;
+ private String mBaseUriPath = "";
+ private String mHead = "";
+
+ private volatile boolean mLocked;
+ private final JButton mScrollLockButton = new JButton();
+
+ private final Workspace mWorkspace;
+
+ /**
+ * Creates a new preview pane that can scroll to the caret position within the
+ * document.
+ *
+ * @param workspace Contains locale and font size information.
+ */
+ public HtmlPreview( final Workspace workspace ) {
+ mWorkspace = workspace;
+
+ // Attempts to prevent a flash of black un-styled content upon load.
+ setStyle( "-fx-background-color: white;" );
+
+ invokeLater( () -> {
+ mHead = generateHead();
+ mView = new HtmlPanel();
+ mScrollPane = new JScrollPane( mView );
+ final var verticalBar = mScrollPane.getVerticalScrollBar();
+ final var verticalPanel = new JPanel( new BorderLayout() );
+
+ final var map = verticalBar.getInputMap( WHEN_IN_FOCUSED_WINDOW );
+ addKeyboardEvents( map );
+
+ mScrollLockButton.setFont( getIconFont( 14 ) );
+ mScrollLockButton.setText( getLockText( mLocked ) );
+ mScrollLockButton.setMargin( new Insets( 1, 0, 0, 0 ) );
+ mScrollLockButton.addActionListener( e -> fireScrollLockEvent( !mLocked ) );
+
+ verticalPanel.add( verticalBar, CENTER );
+ verticalPanel.add( mScrollLockButton, PAGE_END );
+
+ final var wrapper = new JPanel( new BorderLayout() );
+ wrapper.add( mScrollPane, CENTER );
+ wrapper.add( verticalPanel, LINE_END );
+
+ // Enabling the cache attempts to prevent black flashes when resizing.
+ setCache( true );
+ setCacheHint( SPEED );
+ setContent( wrapper );
+
+ final var context = mView.getSharedContext();
+ final var textRenderer = context.getTextRenderer();
+ context.setReplacedElementFactory( FACTORY );
+ textRenderer.setSmoothingThreshold( 0 );
+
+ localeProperty().addListener( ( c, o, n ) -> rerender() );
+ fontFamilyProperty().addListener( ( c, o, n ) -> rerender() );
+ fontSizeProperty().addListener( ( c, o, n ) -> rerender() );
+ } );
+
+ register( this );
+ }
+
+ @Subscribe
+ public void handle( final ScrollLockEvent event ) {
+ mLocked = event.isLocked();
+ invokeLater( () -> mScrollLockButton.setText( getLockText( mLocked ) ) );
+ }
+
+ /**
+ * Updates the internal HTML source shown in the preview pane.
+ *
+ * @param html The new HTML document to display.
+ */
+ public void render( final String html ) {
+ mView.render( decorate( html ), getBaseUri() );
+ }
+
+ /**
+ * Clears the caches then re-renders the content.
+ */
+ public void refresh() {
+ FACTORY.clearCache();
+ rerender();
+ }
+
+ /**
+ * Recomputes the HTML head then renders the document.
+ */
+ private void rerender() {
+ mHead = generateHead();
+ render( mDocument.toString() );
+ }
+
+ /**
+ * Attaches the HTML head prefix and HTML tail suffix to the given HTML
+ * string.
+ *
+ * @param html The HTML to adorn with opening and closing tags.
+ * @return A complete HTML document, ready for rendering.
+ */
+ private String decorate( final String html ) {
+ mDocument.setLength( 0 );
+ mDocument.append( html );
+
+ // Head and tail must be separate from document due to re-rendering.
+ return mHead + mDocument + HTML_TAIL;
+ }
+
+ /**
+ * Called when settings are changed that affect the HTML document preamble.
+ * This is a minor performance optimization to avoid generating the head
+ * each time that the document itself changes.
+ *
+ * @return A new doctype and HTML {@code head} element.
+ */
+ private String generateHead() {
+ final var locale = getLocale();
+ final var url = toUrl( locale );
+ final var base = getBaseUri();
+
+ // Point sizes are converted to pixels because of a rendering bug.
+ return format(
+ HTML_HEAD,
+ locale.getLanguage(),
+ format( HTML_STYLESHEET, HTML_STYLE_PREVIEW ),
+ url == null ? "" : format( HTML_STYLESHEET, url ),
+ getFontFamily(),
+ toPixels( getFontSize() ),
+ base.isBlank() ? "" : format( HTML_BASE, base )
+ );
+ }
+
+ /**
+ * Clears the preview pane by rendering an empty string.
+ */
+ public void clear() {
+ render( "" );
+ }
+
+ /**
+ * Sets the base URI to the containing directory the file being edited.
+ *
+ * @param path The path to the file being edited.
+ */
+ public void setBaseUri( final Path path ) {
+ final var parent = path.getParent();
+ mBaseUriPath = parent == null ? "" : parent.toUri().toString();
+ }
+
+ /**
+ * Scrolls to the closest element matching the given identifier without
+ * waiting for the document to be ready.
+ *
+ * @param id Scroll the preview pane to this unique paragraph identifier.
+ */
+ public void scrollTo( final String id ) {
+ if( mLocked ) {
+ return;
+ }
+
+ invokeLater( () -> {
+ int iter = 0;
+ Box box = null;
+
+ while( iter++ < 3 && ((box = mView.getBoxById( id )) == null) ) {
+ try {
+ sleep( 10 );
+ } catch( final Exception ex ) {
+ clue( ex );
+ }
+ }
+
+ scrollTo( box );
+ } );
+ }
+
+ /**
+ * Scrolls to the location specified by the {@link Box} that corresponds
+ * to a point somewhere in the preview pane. If there is no caret, then
+ * this will not change the scroll position. Changing the scroll position
+ * to the top if the {@link Box} instance is {@code null} will result in
+ * jumping around a lot and inconsistent synchronization issues.
+ *
+ * @param box The rectangular region containing the caret, or {@code null}
+ * if the HTML does not have a caret.
+ */
+ private void scrollTo( final Box box ) {
+ if( box != null ) {
+ invokeLater( () -> {
+ mView.scrollTo( createPoint( box ) );
+ getScrollPane().repaint();
+ } );
+ }
+ }
+
+ /**
+ * Creates a {@link Point} to use as a reference for scrolling to the area
+ * described by the given {@link Box}. The {@link Box} coordinates are used
+ * to populate the {@link Point}'s location, with minor adjustments for
+ * vertical centering.
+ *
+ * @param box The {@link Box} that represents a scrolling anchor reference.
+ * @return A coordinate suitable for scrolling to.
+ */
+ private Point createPoint( final Box box ) {
+ assert box != null;
+
+ // Scroll back up by half the height of the scroll bar to keep the typing
+ // area within the view port. Otherwise the view port will have jumped too
+ // high up and the most recently typed letters won't be visible.
+ int y = max( box.getAbsY() - getVerticalScrollBarHeight() / 2, 0 );
+ int x = box.getAbsX();
+
+ if( !box.getStyle().isInline() ) {
+ final var margin = box.getMargin( mView.getLayoutContext() );
+ y += margin.top();
+ x += margin.left();
+ }
+
+ return new Point( x, y );
+ }
+
+ private String getBaseUri() {
+ return mBaseUriPath;
+ }
+
+ private JScrollPane getScrollPane() {
+ return mScrollPane;
+ }
+
+ public JScrollBar getVerticalScrollBar() {
+ return getScrollPane().getVerticalScrollBar();
+ }
+
+ private int getVerticalScrollBarHeight() {
+ return getVerticalScrollBar().getHeight();
+ }
+
+ /**
+ * Returns the ISO 639 alpha-2 or alpha-3 language code followed by a hyphen
+ * followed by the ISO 15924 alpha-4 script code, followed by an ISO 3166
+ * alpha-2 country code or UN M.49 numeric-3 area code. For example, this
+ * could return "en-Latn-CA" for Canadian English written in the Latin
+ * character set.
+ *
+ * @return Unique identifier for language and country.
+ */
+ private static URL toUrl( final Locale locale ) {
+ return toUrl(
+ get(
+ sSettings.getSetting( STYLESHEET_PREVIEW_LOCALE, "" ),
+ locale.getLanguage(),
+ locale.getScript(),
+ locale.getCountry()
+ )
+ );
+ }
+
+ private static URL toUrl( final String path ) {
+ return HtmlPreview.class.getResource( path );
+ }
+
+ private Locale getLocale() {
+ return localeProperty().toLocale();
+ }
+
+ private LocaleProperty localeProperty() {
+ return mWorkspace.localeProperty( KEY_LANGUAGE_LOCALE );
+ }
+
+ private String getFontFamily() {
+ return fontFamilyProperty().get();
+ }
+
+ private StringProperty fontFamilyProperty() {
+ return mWorkspace.stringProperty( KEY_UI_FONT_PREVIEW_NAME );
+ }
+
+ private double getFontSize() {
+ return fontSizeProperty().get();
+ }
+
+ /**
+ * Returns the font size in points.
+ *
+ * @return The user-defined font size (in pt).
+ */
+ private DoubleProperty fontSizeProperty() {
+ return mWorkspace.doubleProperty( KEY_UI_FONT_PREVIEW_SIZE );
+ }
+
+ private String getLockText( final boolean locked ) {
+ return Character.toString( (locked ? LOCK : UNLOCK_ALT).getChar() );
+ }
+
+ /**
+ * Maps keyboard events to scrollbar commands so that users may control
+ * the {@link HtmlPreview} panel using the keyboard.
+ *
+ * @param map The map to update with keyboard events.
+ */
+ private void addKeyboardEvents( final InputMap map ) {
+ map.put( getKeyStroke( VK_DOWN, 0 ), "positiveUnitIncrement" );
+ map.put( getKeyStroke( VK_UP, 0 ), "negativeUnitIncrement" );
+ map.put( getKeyStroke( VK_PAGE_DOWN, 0 ), "positiveBlockIncrement" );
+ map.put( getKeyStroke( VK_PAGE_UP, 0 ), "negativeBlockIncrement" );
+ map.put( getKeyStroke( VK_HOME, 0 ), "minScroll" );
+ map.put( getKeyStroke( VK_END, 0 ), "maxScroll" );
}
}
src/test/java/com/keenwrite/quotes/SmartQuotesTest.java
package com.keenwrite.quotes;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
*/
public class SmartQuotesTest {
- @Test
+ @Disabled
+ @SuppressWarnings( "unused" )
public void test_Parse_StraightQuotes_CurlyQuotes() throws IOException {
final var fixer = new SmartQuotes();
Delta409 lines added, 370 lines removed, 39-line increase