Dave Jarvis' Repositories

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

Remove AbstractFileFactory

AuthorDaveJarvis <email>
Date2022-01-02 13:22:19 GMT-0800
Commit46c7c784ac9ad4ea9f6a79aefe3d4fd150f1146c
Parent13b94f3
Delta110 lines added, 353 lines removed, 243-line decrease
src/test/java/com/keenwrite/processors/markdown/ImageLinkExtensionTest.java
import com.keenwrite.AwaitFxExtension;
-import com.keenwrite.Caret;
+import com.keenwrite.editors.common.Caret;
import com.keenwrite.preferences.Workspace;
import com.keenwrite.processors.Processor;
src/main/java/com/keenwrite/ui/tree/AltTreeView.java
package com.keenwrite.ui.tree;
+import com.keenwrite.ui.cells.AltTreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
src/main/java/com/keenwrite/ui/cells/CellEditor.java
+package com.keenwrite.ui.cells;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.Property;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.event.EventHandler;
+import javafx.scene.Node;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TextField;
+import javafx.scene.control.TreeCell;
+import javafx.scene.input.KeyEvent;
+
+import java.util.function.Consumer;
+
+import static javafx.application.Platform.runLater;
+import static javafx.scene.input.KeyCode.ENTER;
+import static javafx.scene.input.KeyCode.TAB;
+import static javafx.scene.input.KeyEvent.KEY_RELEASED;
+
+public class CellEditor {
+ private FocusListener mFocusListener;
+ private final KeyHandler mKeyHandler = new KeyHandler();
+ private final Property<String> mInputText = new SimpleStringProperty();
+ private final Consumer<String> mConsumer;
+
+ /**
+ * Responsible for accepting the text when users press the Enter or Tab key.
+ */
+ private class KeyHandler implements EventHandler<KeyEvent> {
+ @Override
+ public void handle( final KeyEvent event ) {
+ if( event.getCode() == ENTER || event.getCode() == TAB ) {
+ commitEdit();
+ event.consume();
+ }
+ }
+ }
+
+ /**
+ * Responsible for committing edits when focus is lost. This will also
+ * deselect the input field when focus is gained so that typing text won't
+ * overwrite the entire existing text.
+ */
+ private class FocusListener implements ChangeListener<Boolean> {
+ private final TextField mInput;
+
+ private FocusListener( final TextField input ) {
+ mInput = input;
+ }
+
+ @Override
+ public void changed(
+ final ObservableValue<? extends Boolean> c,
+ final Boolean endedFocus, final Boolean beganFocus ) {
+
+ if( beganFocus ) {
+ runLater( mInput::deselect );
+ }
+ else if( endedFocus ) {
+ commitEdit();
+ }
+ }
+ }
+
+ /**
+ * Generalized cell editor suitable for use with {@link TableCell} or
+ * {@link TreeCell} instances.
+ *
+ * @param consumer Converts the field input text to the required
+ * data type.
+ * @param graphicProperty Defines the graphical user input field.
+ */
+ public CellEditor(
+ final Consumer<String> consumer,
+ final ObjectProperty<Node> graphicProperty ) {
+ assert consumer != null;
+ mConsumer = consumer;
+
+ init( graphicProperty );
+ }
+
+ private void init( final ObjectProperty<Node> graphicProperty ) {
+ // When the text field is added as the graphics context, we hook into
+ // the changed value to get a handle on the text field. From there it is
+ // possible to add change the keyboard and focus behaviours.
+ graphicProperty.addListener( ( c, o, n ) -> {
+ if( o instanceof TextField ) {
+ o.removeEventHandler( KEY_RELEASED, mKeyHandler );
+ o.focusedProperty().removeListener( mFocusListener );
+ }
+
+ if( n instanceof final TextField input ) {
+ n.addEventFilter( KEY_RELEASED, mKeyHandler );
+ mInputText.bind( input.textProperty() );
+ mFocusListener = new FocusListener( input );
+ n.focusedProperty().addListener( mFocusListener );
+ }
+ } );
+ }
+
+ private void commitEdit() {
+ mConsumer.accept( mInputText.getValue() );
+ }
+}
src/main/java/com/keenwrite/MainPane.java
import com.keenwrite.editors.TextEditor;
import com.keenwrite.editors.TextResource;
+import com.keenwrite.editors.common.ScrollEventHandler;
+import com.keenwrite.editors.common.VariableNameInjector;
import com.keenwrite.editors.definition.DefinitionEditor;
import com.keenwrite.editors.definition.TreeTransformer;
src/main/java/com/keenwrite/PermissiveCertificate.java
-/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
-package com.keenwrite;
-
-import javax.net.ssl.*;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
-
-import static javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier;
-import static javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory;
-
-/**
- * Responsible for trusting all certificate chains. The purpose of this class
- * is to work-around certificate issues caused by software that blocks
- * HTTP requests. For example, zscaler may block HTTP requests to kroki.io
- * when generating diagrams.
- */
-public final class PermissiveCertificate {
- /**
- * Create a trust manager that does not validate certificate chains.
- */
- private final static TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{
- new X509TrustManager() {
- @Override
- public X509Certificate[] getAcceptedIssuers() {
- return new X509Certificate[ 0 ];
- }
-
- @Override
- public void checkClientTrusted(
- X509Certificate[] certs, String authType ) {
- }
-
- @Override
- public void checkServerTrusted(
- X509Certificate[] certs, String authType ) {
- }
- }
- };
-
- /**
- * Responsible for permitting all hostnames for making HTTP requests.
- */
- private static class PermissiveHostNameVerifier implements HostnameVerifier {
- @Override
- public boolean verify( final String hostname, final SSLSession session ) {
- return true;
- }
- }
-
- /**
- * Install the all-trusting trust manager. If this fails it means that in
- * certain situations the HTML preview may fail to render diagrams. A way
- * to work around the issue is to install a local server for generating
- * diagrams.
- */
- public static boolean installTrustManager() {
- try {
- final var context = SSLContext.getInstance( "SSL" );
- context.init( null, TRUST_ALL_CERTS, new SecureRandom() );
- setDefaultSSLSocketFactory( context.getSocketFactory() );
- setDefaultHostnameVerifier( new PermissiveHostNameVerifier() );
- return true;
- } catch( final Exception ex ) {
- return false;
- }
- }
-
- /**
- * Use {@link #installTrustManager()}.
- */
- private PermissiveCertificate() {
- }
-}
src/main/java/com/keenwrite/ScrollEventHandler.java
-/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
-package com.keenwrite;
-
-import com.keenwrite.events.ScrollLockEvent;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.SimpleBooleanProperty;
-import javafx.event.Event;
-import javafx.event.EventHandler;
-import javafx.scene.control.ScrollBar;
-import javafx.scene.control.skin.ScrollBarSkin;
-import javafx.scene.input.MouseEvent;
-import javafx.scene.input.ScrollEvent;
-import javafx.scene.layout.StackPane;
-import org.fxmisc.flowless.VirtualizedScrollPane;
-import org.fxmisc.richtext.StyleClassedTextArea;
-import org.greenrobot.eventbus.Subscribe;
-
-import javax.swing.*;
-import java.util.function.Consumer;
-
-import static com.keenwrite.events.Bus.register;
-import static java.lang.Math.max;
-import static java.lang.Math.min;
-import static javafx.geometry.Orientation.VERTICAL;
-import static javax.swing.SwingUtilities.invokeLater;
-
-/**
- * Converts scroll events from {@link VirtualizedScrollPane} scroll bars to
- * an instance of {@link JScrollBar}.
- * <p>
- * Called to synchronize the scrolling areas for either scrolling with the
- * mouse or scrolling using the scrollbar's thumb. Both are required to avoid
- * scrolling on the estimatedScrollYProperty that occurs when text events
- * fire. Scrolling performed for text events are handled separately to ensure
- * the preview panel scrolls to the same position in the Markdown editor,
- * taking into account things like images, tables, and other potentially
- * long vertical presentation items.
- * </p>
- */
-public final class ScrollEventHandler implements EventHandler<Event> {
-
- private final class MouseHandler implements EventHandler<MouseEvent> {
- private final EventHandler<? super MouseEvent> mOldHandler;
-
- /**
- * Constructs a new handler for mouse scrolling events.
- *
- * @param oldHandler Receives the event after scrolling takes place.
- */
- private MouseHandler( final EventHandler<? super MouseEvent> oldHandler ) {
- mOldHandler = oldHandler;
- }
-
- @Override
- public void handle( final MouseEvent event ) {
- ScrollEventHandler.this.handle( event );
- mOldHandler.handle( event );
- }
- }
-
- private final class ScrollHandler implements EventHandler<ScrollEvent> {
- @Override
- public void handle( final ScrollEvent event ) {
- ScrollEventHandler.this.handle( event );
- }
- }
-
- private final VirtualizedScrollPane<StyleClassedTextArea> mEditorScrollPane;
- private final JScrollBar mPreviewScrollBar;
- private final BooleanProperty mEnabled = new SimpleBooleanProperty();
-
- private boolean mLocked;
-
- /**
- * @param editorScrollPane Scroll event source (human movement).
- * @param previewScrollBar Scroll event destination (corresponding movement).
- */
- public ScrollEventHandler(
- final VirtualizedScrollPane<StyleClassedTextArea> editorScrollPane,
- final JScrollBar previewScrollBar ) {
- mEditorScrollPane = editorScrollPane;
- mPreviewScrollBar = previewScrollBar;
-
- mEditorScrollPane.addEventFilter( ScrollEvent.ANY, new ScrollHandler() );
-
- initVerticalScrollBarThumb(
- mEditorScrollPane,
- thumb -> {
- final var handler = new MouseHandler( thumb.getOnMouseDragged() );
- thumb.setOnMouseDragged( handler );
- }
- );
-
- register( this );
- }
-
- /**
- * Gets a property intended to be bound to selected property of the tab being
- * scrolled. This is required because there's only one preview pane but
- * multiple editor panes. Each editor pane maintains its own scroll position.
- *
- * @return A {@link BooleanProperty} representing whether the scroll
- * events for this tab are to be executed.
- */
- public BooleanProperty enabledProperty() {
- return mEnabled;
- }
-
- /**
- * Scrolls the preview scrollbar relative to the edit scrollbar. Algorithm
- * is based on Karl Tauber's ratio calculation.
- *
- * @param event Unused; either {@link MouseEvent} or {@link ScrollEvent}
- */
- @Override
- public void handle( final Event event ) {
- invokeLater( () -> {
- if( isEnabled() ) {
- // e is for editor pane
- final var eScrollPane = getEditorScrollPane();
- final var eScrollY =
- eScrollPane.estimatedScrollYProperty().getValue().intValue();
- final var eHeight = (int)
- (eScrollPane.totalHeightEstimateProperty().getValue().intValue()
- - eScrollPane.getHeight());
- final var eRatio = eHeight > 0
- ? min( max( eScrollY / (float) eHeight, 0 ), 1 ) : 0;
-
- // p is for preview pane
- final var pScrollBar = getPreviewScrollBar();
- final var pHeight = pScrollBar.getMaximum() - pScrollBar.getHeight();
- final var pScrollY = (int) (pHeight * eRatio);
-
- pScrollBar.setValue( pScrollY );
- pScrollBar.getParent().repaint();
- }
- } );
- }
-
- @Subscribe
- public void handle( final ScrollLockEvent event ) {
- mLocked = event.isLocked();
- }
-
- private void initVerticalScrollBarThumb(
- final VirtualizedScrollPane<StyleClassedTextArea> pane,
- final Consumer<StackPane> consumer ) {
- // When the skin property is set, the stack pane is available (not null).
- getVerticalScrollBar( pane ).skinProperty().addListener( ( c, o, n ) -> {
- for( final var node : ((ScrollBarSkin) n).getChildren() ) {
- // Brittle, but what can you do?
- if( node.getStyleClass().contains( "thumb" ) ) {
- consumer.accept( (StackPane) node );
- }
- }
- } );
- }
-
- /**
- * Returns the vertical {@link ScrollBar} instance associated with the
- * given scroll pane. This is {@code null}-safe because the scroll pane
- * initializes its vertical {@link ScrollBar} upon construction.
- *
- * @param pane The scroll pane that contains a vertical {@link ScrollBar}.
- * @return The vertical {@link ScrollBar} associated with the scroll pane.
- * @throws IllegalStateException Could not obtain the vertical scroll bar.
- */
- private ScrollBar getVerticalScrollBar(
- final VirtualizedScrollPane<StyleClassedTextArea> pane ) {
-
- for( final var node : pane.getChildrenUnmodifiable() ) {
- if( node instanceof final ScrollBar scrollBar &&
- scrollBar.getOrientation() == VERTICAL ) {
- return scrollBar;
- }
- }
-
- throw new IllegalStateException( "No vertical scroll bar found." );
- }
-
- private boolean isEnabled() {
- // TODO: As a minor optimization, when this is set to false, it could remove
- // the MouseHandler and ScrollHandler so that events only dispatch to one
- // object (instead of one per editor tab).
- return mEnabled.get() && !mLocked;
- }
-
- private VirtualizedScrollPane<StyleClassedTextArea> getEditorScrollPane() {
- return mEditorScrollPane;
- }
-
- private JScrollBar getPreviewScrollBar() {
- return mPreviewScrollBar;
- }
-}
src/main/java/com/keenwrite/VariableNameInjector.java
-/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
-package com.keenwrite;
-
-import com.keenwrite.editors.TextDefinition;
-import com.keenwrite.editors.TextEditor;
-import com.keenwrite.editors.definition.DefinitionTreeItem;
-
-import java.util.function.UnaryOperator;
-
-import static com.keenwrite.constants.Constants.*;
-import static com.keenwrite.events.StatusEvent.clue;
-
-/**
- * Provides the logic for injecting variable names within the editor.
- */
-public final class VariableNameInjector {
-
- /**
- * Find a node that matches the current word and substitute the definition
- * reference.
- */
- public static void autoinsert(
- final TextEditor editor,
- final TextDefinition definitions,
- final UnaryOperator<String> operator ) {
- assert editor != null;
- assert definitions != null;
- assert operator != null;
-
- try {
- if( definitions.isEmpty() ) {
- clue( STATUS_DEFINITION_EMPTY );
- }
- else {
- final var indexes = editor.getCaretWord();
- final var word = editor.getText( indexes );
-
- if( word.isBlank() ) {
- clue( STATUS_DEFINITION_BLANK );
- }
- else {
- final var leaf = findLeaf( definitions, word );
-
- if( leaf == null ) {
- clue( STATUS_DEFINITION_MISSING, word );
- }
- else {
- editor.replaceText( indexes, operator.apply( leaf.toPath() ) );
- definitions.expand( leaf );
- }
- }
- }
- } catch( final Exception ex ) {
- clue( STATUS_DEFINITION_BLANK, ex );
- }
- }
-
- /**
- * Looks for the given word, matching first by exact, next by a starts-with
- * condition with diacritics replaced, then by containment.
- *
- * @param word Match the word by: exact, beginning, containment, or other.
- */
- @SuppressWarnings( "ConstantConditions" )
- private static DefinitionTreeItem<String> findLeaf(
- final TextDefinition definition, final String word ) {
- assert definition != null;
- assert word != null;
-
- DefinitionTreeItem<String> leaf = null;
-
- leaf = leaf == null ? definition.findLeafExact( word ) : leaf;
- leaf = leaf == null ? definition.findLeafStartsWith( word ) : leaf;
- leaf = leaf == null ? definition.findLeafContains( word ) : leaf;
- leaf = leaf == null ? definition.findLeafContainsNoCase( word ) : leaf;
-
- return leaf;
- }
-
- /**
- * Prevent instantiation.
- */
- private VariableNameInjector() {}
-}