| Author | DaveJarvis <email> |
|---|---|
| Date | 2021-12-23 01:08:49 GMT-0800 |
| Commit | 97661c4b65298584571e0e31dcbbf255496abd55 |
| Parent | 59741fc |
| Delta | 955 lines added, 854 lines removed, 101-line increase |
| import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG; | ||
| import static com.keenwrite.preferences.AppKeys.*; | ||
| -import static com.keenwrite.preferences.TableField.ofListType; | ||
| -import static com.keenwrite.preferences.LocaleProperty.localeListProperty; | ||
| -import static com.keenwrite.preferences.SkinProperty.skinListProperty; | ||
| -import static javafx.scene.control.ButtonType.CANCEL; | ||
| -import static javafx.scene.control.ButtonType.OK; | ||
| - | ||
| -/** | ||
| - * Provides the ability for users to configure their preferences. This links | ||
| - * the {@link Workspace} model with the {@link PreferencesFx} view, in MVC. | ||
| - */ | ||
| -@SuppressWarnings( "SameParameterValue" ) | ||
| -public final class PreferencesController { | ||
| - | ||
| - private final Workspace mWorkspace; | ||
| - private final PreferencesFx mPreferencesFx; | ||
| - | ||
| - public PreferencesController( final Workspace workspace ) { | ||
| - mWorkspace = workspace; | ||
| - | ||
| - // All properties must be initialized before creating the dialog. | ||
| - mPreferencesFx = createPreferencesFx(); | ||
| - | ||
| - initKeyEventHandler( mPreferencesFx ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Display the user preferences settings dialog (non-modal). | ||
| - */ | ||
| - public void show() { | ||
| - getPreferencesFx().show( false ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Call to persist the settings. Strictly speaking, this could watch on | ||
| - * all values for external changes then save automatically. | ||
| - */ | ||
| - public void save() { | ||
| - getPreferencesFx().saveSettings(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Delegates to the {@link PreferencesFx} event handler for monitoring | ||
| - * save events. | ||
| - * | ||
| - * @param eventHandler The handler to call when the preferences are saved. | ||
| - */ | ||
| - public void addSaveEventHandler( | ||
| - final EventHandler<? super PreferencesFxEvent> eventHandler ) { | ||
| - getPreferencesFx().addEventHandler( EVENT_PREFERENCES_SAVED, eventHandler ); | ||
| - } | ||
| - | ||
| - private StringField createFontNameField( | ||
| - final StringProperty fontName, final DoubleProperty fontSize ) { | ||
| - final var control = new SimpleFontControl( "Change" ); | ||
| - control.fontSizeProperty().addListener( ( c, o, n ) -> { | ||
| - if( n != null ) { | ||
| - fontSize.set( n.doubleValue() ); | ||
| - } | ||
| - } ); | ||
| - return ofStringType( fontName ).render( control ); | ||
| - } | ||
| - | ||
| - private <K, V> TableField<Entry<K, V>> createListEntryField() { | ||
| - final TableField<Entry<K, V>> field = ofListType(); | ||
| - return field.render( new SimpleTableControl<>() ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Creates the preferences dialog based using {@link XmlStorageHandler} and | ||
| - * numerous {@link Category} objects. | ||
| - * | ||
| - * @return A component for editing preferences. | ||
| - * @throws RuntimeException Could not construct the {@link PreferencesFx} | ||
| - * object (e.g., illegal access permissions, | ||
| - * unmapped XML resource). | ||
| - */ | ||
| - private PreferencesFx createPreferencesFx() { | ||
| - return PreferencesFx.of( createStorageHandler(), createCategories() ) | ||
| - .instantPersistent( false ) | ||
| - .dialogIcon( ICON_DIALOG ); | ||
| - } | ||
| - | ||
| - private StorageHandler createStorageHandler() { | ||
| - return new XmlStorageHandler(); | ||
| - } | ||
| - | ||
| - private Category[] createCategories() { | ||
| - return new Category[]{ | ||
| - Category.of( | ||
| - get( KEY_DOC ), | ||
| - Group.of( | ||
| - get( KEY_DOC_META ), | ||
| - Setting.of( label( KEY_DOC_META ) ), | ||
| - Setting.of( title( KEY_DOC_META ), | ||
| - createListEntryField(), | ||
| - listEntryProperty( KEY_DOC_META ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_DOC_TITLE ), | ||
| - Setting.of( label( KEY_DOC_TITLE ) ), | ||
| - Setting.of( title( KEY_DOC_TITLE ), | ||
| - stringProperty( KEY_DOC_TITLE ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_DOC_AUTHOR ), | ||
| - Setting.of( label( KEY_DOC_AUTHOR ) ), | ||
| - Setting.of( title( KEY_DOC_AUTHOR ), | ||
| - stringProperty( KEY_DOC_AUTHOR ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_DOC_BYLINE ), | ||
| - Setting.of( label( KEY_DOC_BYLINE ) ), | ||
| - Setting.of( title( KEY_DOC_BYLINE ), | ||
| - stringProperty( KEY_DOC_BYLINE ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_DOC_ADDRESS ), | ||
| - Setting.of( label( KEY_DOC_ADDRESS ) ), | ||
| - createMultilineSetting( "Address", KEY_DOC_ADDRESS ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_DOC_PHONE ), | ||
| - Setting.of( label( KEY_DOC_PHONE ) ), | ||
| - Setting.of( title( KEY_DOC_PHONE ), | ||
| - stringProperty( KEY_DOC_PHONE ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_DOC_EMAIL ), | ||
| - Setting.of( label( KEY_DOC_EMAIL ) ), | ||
| - Setting.of( title( KEY_DOC_EMAIL ), | ||
| - stringProperty( KEY_DOC_EMAIL ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_DOC_KEYWORDS ), | ||
| - Setting.of( label( KEY_DOC_KEYWORDS ) ), | ||
| - Setting.of( title( KEY_DOC_KEYWORDS ), | ||
| - stringProperty( KEY_DOC_KEYWORDS ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_DOC_COPYRIGHT ), | ||
| - Setting.of( label( KEY_DOC_COPYRIGHT ) ), | ||
| - Setting.of( title( KEY_DOC_COPYRIGHT ), | ||
| - stringProperty( KEY_DOC_COPYRIGHT ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_DOC_DATE ), | ||
| - Setting.of( label( KEY_DOC_DATE ) ), | ||
| - Setting.of( title( KEY_DOC_DATE ), | ||
| - stringProperty( KEY_DOC_DATE ) ) | ||
| - ) | ||
| - ), | ||
| - Category.of( | ||
| - get( KEY_TYPESET ), | ||
| - Group.of( | ||
| - get( KEY_TYPESET_CONTEXT ), | ||
| - Setting.of( label( KEY_TYPESET_CONTEXT_THEMES_PATH ) ), | ||
| - Setting.of( title( KEY_TYPESET_CONTEXT_THEMES_PATH ), | ||
| - fileProperty( KEY_TYPESET_CONTEXT_THEMES_PATH ), true ), | ||
| - Setting.of( label( KEY_TYPESET_CONTEXT_CLEAN ) ), | ||
| - Setting.of( title( KEY_TYPESET_CONTEXT_CLEAN ), | ||
| - booleanProperty( KEY_TYPESET_CONTEXT_CLEAN ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_TYPESET_TYPOGRAPHY ), | ||
| - Setting.of( label( KEY_TYPESET_TYPOGRAPHY_QUOTES ) ), | ||
| - Setting.of( title( KEY_TYPESET_TYPOGRAPHY_QUOTES ), | ||
| - booleanProperty( KEY_TYPESET_TYPOGRAPHY_QUOTES ) ) | ||
| - ) | ||
| - ), | ||
| - Category.of( | ||
| - get( KEY_EDITOR ), | ||
| - Group.of( | ||
| - get( KEY_EDITOR_AUTOSAVE ), | ||
| - Setting.of( label( KEY_EDITOR_AUTOSAVE ) ), | ||
| - Setting.of( title( KEY_EDITOR_AUTOSAVE ), | ||
| - integerProperty( KEY_EDITOR_AUTOSAVE ) ) | ||
| - ) | ||
| - ), | ||
| - Category.of( | ||
| - get( KEY_R ), | ||
| - Group.of( | ||
| - get( KEY_R_DIR ), | ||
| - Setting.of( label( KEY_R_DIR, | ||
| - stringProperty( KEY_DEF_DELIM_BEGAN ).get(), | ||
| - stringProperty( KEY_DEF_DELIM_ENDED ).get() ) ), | ||
| - Setting.of( title( KEY_R_DIR ), | ||
| - fileProperty( KEY_R_DIR ), true ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_R_SCRIPT ), | ||
| - Setting.of( label( KEY_R_SCRIPT ) ), | ||
| - createMultilineSetting( "Script", KEY_R_SCRIPT ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_R_DELIM_BEGAN ), | ||
| - Setting.of( label( KEY_R_DELIM_BEGAN ) ), | ||
| - Setting.of( title( KEY_R_DELIM_BEGAN ), | ||
| - stringProperty( KEY_R_DELIM_BEGAN ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_R_DELIM_ENDED ), | ||
| - Setting.of( label( KEY_R_DELIM_ENDED ) ), | ||
| - Setting.of( title( KEY_R_DELIM_ENDED ), | ||
| - stringProperty( KEY_R_DELIM_ENDED ) ) | ||
| - ) | ||
| - ), | ||
| - Category.of( | ||
| - get( KEY_IMAGES ), | ||
| - Group.of( | ||
| - get( KEY_IMAGES_DIR ), | ||
| - Setting.of( label( KEY_IMAGES_DIR ) ), | ||
| - Setting.of( title( KEY_IMAGES_DIR ), | ||
| - fileProperty( KEY_IMAGES_DIR ), true ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_IMAGES_ORDER ), | ||
| - Setting.of( label( KEY_IMAGES_ORDER ) ), | ||
| - Setting.of( title( KEY_IMAGES_ORDER ), | ||
| - stringProperty( KEY_IMAGES_ORDER ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_IMAGES_RESIZE ), | ||
| - Setting.of( label( KEY_IMAGES_RESIZE ) ), | ||
| - Setting.of( title( KEY_IMAGES_RESIZE ), | ||
| - booleanProperty( KEY_IMAGES_RESIZE ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_IMAGES_SERVER ), | ||
| - Setting.of( label( KEY_IMAGES_SERVER ) ), | ||
| - Setting.of( title( KEY_IMAGES_SERVER ), | ||
| - stringProperty( KEY_IMAGES_SERVER ) ) | ||
| - ) | ||
| - ), | ||
| - Category.of( | ||
| - get( KEY_DEF ), | ||
| - Group.of( | ||
| - get( KEY_DEF_PATH ), | ||
| - Setting.of( label( KEY_DEF_PATH ) ), | ||
| - Setting.of( title( KEY_DEF_PATH ), | ||
| - fileProperty( KEY_DEF_PATH ), false ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_DEF_DELIM_BEGAN ), | ||
| - Setting.of( label( KEY_DEF_DELIM_BEGAN ) ), | ||
| - Setting.of( title( KEY_DEF_DELIM_BEGAN ), | ||
| - stringProperty( KEY_DEF_DELIM_BEGAN ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_DEF_DELIM_ENDED ), | ||
| - Setting.of( label( KEY_DEF_DELIM_ENDED ) ), | ||
| - Setting.of( title( KEY_DEF_DELIM_ENDED ), | ||
| - stringProperty( KEY_DEF_DELIM_ENDED ) ) | ||
| - ) | ||
| - ), | ||
| - Category.of( | ||
| - get( KEY_UI_FONT ), | ||
| - Group.of( | ||
| - get( KEY_UI_FONT_EDITOR ), | ||
| - Setting.of( label( KEY_UI_FONT_EDITOR_NAME ) ), | ||
| - Setting.of( title( KEY_UI_FONT_EDITOR_NAME ), | ||
| - createFontNameField( | ||
| - stringProperty( KEY_UI_FONT_EDITOR_NAME ), | ||
| - doubleProperty( KEY_UI_FONT_EDITOR_SIZE ) ), | ||
| - stringProperty( KEY_UI_FONT_EDITOR_NAME ) ), | ||
| - Setting.of( label( KEY_UI_FONT_EDITOR_SIZE ) ), | ||
| - Setting.of( title( KEY_UI_FONT_EDITOR_SIZE ), | ||
| - doubleProperty( KEY_UI_FONT_EDITOR_SIZE ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_UI_FONT_PREVIEW ), | ||
| - Setting.of( label( KEY_UI_FONT_PREVIEW_NAME ) ), | ||
| - Setting.of( title( KEY_UI_FONT_PREVIEW_NAME ), | ||
| - createFontNameField( | ||
| - stringProperty( KEY_UI_FONT_PREVIEW_NAME ), | ||
| - doubleProperty( KEY_UI_FONT_PREVIEW_SIZE ) ), | ||
| - stringProperty( KEY_UI_FONT_PREVIEW_NAME ) ), | ||
| - Setting.of( label( KEY_UI_FONT_PREVIEW_SIZE ) ), | ||
| - Setting.of( title( KEY_UI_FONT_PREVIEW_SIZE ), | ||
| - doubleProperty( KEY_UI_FONT_PREVIEW_SIZE ) ), | ||
| - Setting.of( label( KEY_UI_FONT_PREVIEW_MONO_NAME ) ), | ||
| - Setting.of( title( KEY_UI_FONT_PREVIEW_MONO_NAME ), | ||
| - createFontNameField( | ||
| - stringProperty( KEY_UI_FONT_PREVIEW_MONO_NAME ), | ||
| - doubleProperty( KEY_UI_FONT_PREVIEW_MONO_SIZE ) ), | ||
| - stringProperty( KEY_UI_FONT_PREVIEW_MONO_NAME ) ), | ||
| - Setting.of( label( KEY_UI_FONT_PREVIEW_MONO_SIZE ) ), | ||
| - Setting.of( title( KEY_UI_FONT_PREVIEW_MONO_SIZE ), | ||
| - doubleProperty( KEY_UI_FONT_PREVIEW_MONO_SIZE ) ) | ||
| - ) | ||
| - ), | ||
| - Category.of( | ||
| - get( KEY_UI_SKIN ), | ||
| - Group.of( | ||
| - get( KEY_UI_SKIN_SELECTION ), | ||
| - Setting.of( label( KEY_UI_SKIN_SELECTION ) ), | ||
| - Setting.of( title( KEY_UI_SKIN_SELECTION ), | ||
| - skinListProperty(), | ||
| - skinProperty( KEY_UI_SKIN_SELECTION ) ) | ||
| - ), | ||
| - Group.of( | ||
| - get( KEY_UI_SKIN_CUSTOM ), | ||
| - Setting.of( label( KEY_UI_SKIN_CUSTOM ) ), | ||
| - Setting.of( title( KEY_UI_SKIN_CUSTOM ), | ||
| - fileProperty( KEY_UI_SKIN_CUSTOM ), false ) | ||
| - ) | ||
| - ), | ||
| - Category.of( | ||
| - get( KEY_UI_PREVIEW ), | ||
| - Group.of( | ||
| - get( KEY_UI_PREVIEW_STYLESHEET ), | ||
| - Setting.of( label( KEY_UI_PREVIEW_STYLESHEET ) ), | ||
| - Setting.of( title( KEY_UI_PREVIEW_STYLESHEET ), | ||
| - fileProperty( KEY_UI_PREVIEW_STYLESHEET ), false ) | ||
| - ) | ||
| - ), | ||
| - Category.of( | ||
| - get( KEY_LANGUAGE ), | ||
| - Group.of( | ||
| - get( KEY_LANGUAGE_LOCALE ), | ||
| - Setting.of( label( KEY_LANGUAGE_LOCALE ) ), | ||
| - Setting.of( title( KEY_LANGUAGE_LOCALE ), | ||
| - localeListProperty(), | ||
| - localeProperty( KEY_LANGUAGE_LOCALE ) ) | ||
| - ) | ||
| - )}; | ||
| - } | ||
| - | ||
| - @SuppressWarnings( "unchecked" ) | ||
| - private Setting<StringField, StringProperty> createMultilineSetting( | ||
| - final String description, final Key property ) { | ||
| - final Setting<StringField, StringProperty> setting = | ||
| - Setting.of( description, stringProperty( property ) ); | ||
| - final var field = setting.getElement(); | ||
| - field.multiline( true ); | ||
| - | ||
| - return setting; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Map ENTER and ESCAPE keys to OK and CANCEL buttons, respectively. | ||
| - */ | ||
| - private void initKeyEventHandler( final PreferencesFx preferences ) { | ||
| - final var view = preferences.getView(); | ||
| - final var nodes = view.getChildrenUnmodifiable(); | ||
| - final var master = (MasterDetailPane) nodes.get( 0 ); | ||
| - final var detail = (NavigationView) master.getDetailNode(); | ||
| - final var pane = (DialogPane) view.getParent(); | ||
| - | ||
| - detail.setOnKeyReleased( key -> { | ||
| - switch( key.getCode() ) { | ||
| - case ENTER -> ((Button) pane.lookupButton( OK )).fire(); | ||
| - case ESCAPE -> ((Button) pane.lookupButton( CANCEL )).fire(); | ||
| - } | ||
| - } ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Creates a label for the given key after interpolating its value. | ||
| - * | ||
| - * @param key The key to find in the resource bundle. | ||
| - * @return The value of the key as a label. | ||
| - */ | ||
| - private Node label( final Key key ) { | ||
| - return label( key, (String[]) null ); | ||
| - } | ||
| - | ||
| - private Node label( final Key key, final String... values ) { | ||
| - return new Label( get( key.toString() + ".desc", (Object[]) values ) ); | ||
| - } | ||
| - | ||
| - private String title( final Key key ) { | ||
| - return get( key.toString() + ".title" ); | ||
| - } | ||
| - | ||
| - private ObjectProperty<File> fileProperty( final Key key ) { | ||
| - return mWorkspace.fileProperty( key ); | ||
| - } | ||
| - | ||
| - private StringProperty stringProperty( final Key key ) { | ||
| - return mWorkspace.stringProperty( key ); | ||
| - } | ||
| - | ||
| - private BooleanProperty booleanProperty( final Key key ) { | ||
| - return mWorkspace.booleanProperty( key ); | ||
| - } | ||
| - | ||
| - @SuppressWarnings( "SameParameterValue" ) | ||
| - private IntegerProperty integerProperty( final Key key ) { | ||
| - return mWorkspace.integerProperty( key ); | ||
| - } | ||
| - | ||
| - @SuppressWarnings( "SameParameterValue" ) | ||
| - private DoubleProperty doubleProperty( final Key key ) { | ||
| - return mWorkspace.doubleProperty( key ); | ||
| - } | ||
| - | ||
| - private ObjectProperty<String> skinProperty( final Key key ) { | ||
| - return mWorkspace.skinProperty( key ); | ||
| - } | ||
| - | ||
| - private ObjectProperty<String> localeProperty( final Key key ) { | ||
| - return mWorkspace.localeProperty( key ); | ||
| - } | ||
| - | ||
| - private <E> SetProperty<E> listEntryProperty( final Key key ) { | ||
| - return mWorkspace.setsProperty( key ); | ||
| +import static com.keenwrite.preferences.LocaleProperty.localeListProperty; | ||
| +import static com.keenwrite.preferences.SkinProperty.skinListProperty; | ||
| +import static com.keenwrite.preferences.TableField.ofListType; | ||
| +import static javafx.scene.control.ButtonType.CANCEL; | ||
| +import static javafx.scene.control.ButtonType.OK; | ||
| + | ||
| +/** | ||
| + * Provides the ability for users to configure their preferences. This links | ||
| + * the {@link Workspace} model with the {@link PreferencesFx} view, in MVC. | ||
| + */ | ||
| +@SuppressWarnings( "SameParameterValue" ) | ||
| +public final class PreferencesController { | ||
| + | ||
| + private final Workspace mWorkspace; | ||
| + private final PreferencesFx mPreferencesFx; | ||
| + | ||
| + public PreferencesController( final Workspace workspace ) { | ||
| + mWorkspace = workspace; | ||
| + | ||
| + // All properties must be initialized before creating the dialog. | ||
| + mPreferencesFx = createPreferencesFx(); | ||
| + | ||
| + initKeyEventHandler( mPreferencesFx ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Display the user preferences settings dialog (non-modal). | ||
| + */ | ||
| + public void show() { | ||
| + getPreferencesFx().show( false ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Call to persist the settings. Strictly speaking, this could watch on | ||
| + * all values for external changes then save automatically. | ||
| + */ | ||
| + public void save() { | ||
| + getPreferencesFx().saveSettings(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Delegates to the {@link PreferencesFx} event handler for monitoring | ||
| + * save events. | ||
| + * | ||
| + * @param eventHandler The handler to call when the preferences are saved. | ||
| + */ | ||
| + public void addSaveEventHandler( | ||
| + final EventHandler<? super PreferencesFxEvent> eventHandler ) { | ||
| + getPreferencesFx().addEventHandler( EVENT_PREFERENCES_SAVED, eventHandler ); | ||
| + } | ||
| + | ||
| + private StringField createFontNameField( | ||
| + final StringProperty fontName, final DoubleProperty fontSize ) { | ||
| + final var control = new SimpleFontControl( "Change" ); | ||
| + control.fontSizeProperty().addListener( ( c, o, n ) -> { | ||
| + if( n != null ) { | ||
| + fontSize.set( n.doubleValue() ); | ||
| + } | ||
| + } ); | ||
| + return ofStringType( fontName ).render( control ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Convenience method to create a helper class for the user interface. This | ||
| + * establishes a key-value pair for the view. | ||
| + * | ||
| + * @param persist A reference to the values that will be persisted. | ||
| + * @param <K> The type of key, usually a string. | ||
| + * @param <V> The type of value, usually a string. | ||
| + * @return UI data model container that may update the persistent state. | ||
| + */ | ||
| + private <K, V> TableField<Entry<K, V>> createTableField( | ||
| + final ListProperty<Entry<K, V>> persist ) { | ||
| + return ofListType( persist ).render( new SimpleTableControl<>() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates the preferences dialog based using {@link XmlStorageHandler} and | ||
| + * numerous {@link Category} objects. | ||
| + * | ||
| + * @return A component for editing preferences. | ||
| + * @throws RuntimeException Could not construct the {@link PreferencesFx} | ||
| + * object (e.g., illegal access permissions, | ||
| + * unmapped XML resource). | ||
| + */ | ||
| + private PreferencesFx createPreferencesFx() { | ||
| + return PreferencesFx.of( createStorageHandler(), createCategories() ) | ||
| + .instantPersistent( false ) | ||
| + .dialogIcon( ICON_DIALOG ); | ||
| + } | ||
| + | ||
| + private StorageHandler createStorageHandler() { | ||
| + return new XmlStorageHandler(); | ||
| + } | ||
| + | ||
| + private Category[] createCategories() { | ||
| + return new Category[]{ | ||
| + Category.of( | ||
| + get( KEY_DOC ), | ||
| + Group.of( | ||
| + get( KEY_DOC_META ), | ||
| + Setting.of( label( KEY_DOC_META ) ), | ||
| + Setting.of( title( KEY_DOC_META ), | ||
| + createTableField( listEntryProperty( KEY_DOC_META ) ), | ||
| + listEntryProperty( KEY_DOC_META ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_DOC_TITLE ), | ||
| + Setting.of( label( KEY_DOC_TITLE ) ), | ||
| + Setting.of( title( KEY_DOC_TITLE ), | ||
| + stringProperty( KEY_DOC_TITLE ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_DOC_AUTHOR ), | ||
| + Setting.of( label( KEY_DOC_AUTHOR ) ), | ||
| + Setting.of( title( KEY_DOC_AUTHOR ), | ||
| + stringProperty( KEY_DOC_AUTHOR ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_DOC_BYLINE ), | ||
| + Setting.of( label( KEY_DOC_BYLINE ) ), | ||
| + Setting.of( title( KEY_DOC_BYLINE ), | ||
| + stringProperty( KEY_DOC_BYLINE ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_DOC_ADDRESS ), | ||
| + Setting.of( label( KEY_DOC_ADDRESS ) ), | ||
| + createMultilineSetting( "Address", KEY_DOC_ADDRESS ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_DOC_PHONE ), | ||
| + Setting.of( label( KEY_DOC_PHONE ) ), | ||
| + Setting.of( title( KEY_DOC_PHONE ), | ||
| + stringProperty( KEY_DOC_PHONE ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_DOC_EMAIL ), | ||
| + Setting.of( label( KEY_DOC_EMAIL ) ), | ||
| + Setting.of( title( KEY_DOC_EMAIL ), | ||
| + stringProperty( KEY_DOC_EMAIL ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_DOC_KEYWORDS ), | ||
| + Setting.of( label( KEY_DOC_KEYWORDS ) ), | ||
| + Setting.of( title( KEY_DOC_KEYWORDS ), | ||
| + stringProperty( KEY_DOC_KEYWORDS ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_DOC_COPYRIGHT ), | ||
| + Setting.of( label( KEY_DOC_COPYRIGHT ) ), | ||
| + Setting.of( title( KEY_DOC_COPYRIGHT ), | ||
| + stringProperty( KEY_DOC_COPYRIGHT ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_DOC_DATE ), | ||
| + Setting.of( label( KEY_DOC_DATE ) ), | ||
| + Setting.of( title( KEY_DOC_DATE ), | ||
| + stringProperty( KEY_DOC_DATE ) ) | ||
| + ) | ||
| + ), | ||
| + Category.of( | ||
| + get( KEY_TYPESET ), | ||
| + Group.of( | ||
| + get( KEY_TYPESET_CONTEXT ), | ||
| + Setting.of( label( KEY_TYPESET_CONTEXT_THEMES_PATH ) ), | ||
| + Setting.of( title( KEY_TYPESET_CONTEXT_THEMES_PATH ), | ||
| + fileProperty( KEY_TYPESET_CONTEXT_THEMES_PATH ), true ), | ||
| + Setting.of( label( KEY_TYPESET_CONTEXT_CLEAN ) ), | ||
| + Setting.of( title( KEY_TYPESET_CONTEXT_CLEAN ), | ||
| + booleanProperty( KEY_TYPESET_CONTEXT_CLEAN ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_TYPESET_TYPOGRAPHY ), | ||
| + Setting.of( label( KEY_TYPESET_TYPOGRAPHY_QUOTES ) ), | ||
| + Setting.of( title( KEY_TYPESET_TYPOGRAPHY_QUOTES ), | ||
| + booleanProperty( KEY_TYPESET_TYPOGRAPHY_QUOTES ) ) | ||
| + ) | ||
| + ), | ||
| + Category.of( | ||
| + get( KEY_EDITOR ), | ||
| + Group.of( | ||
| + get( KEY_EDITOR_AUTOSAVE ), | ||
| + Setting.of( label( KEY_EDITOR_AUTOSAVE ) ), | ||
| + Setting.of( title( KEY_EDITOR_AUTOSAVE ), | ||
| + integerProperty( KEY_EDITOR_AUTOSAVE ) ) | ||
| + ) | ||
| + ), | ||
| + Category.of( | ||
| + get( KEY_R ), | ||
| + Group.of( | ||
| + get( KEY_R_DIR ), | ||
| + Setting.of( label( KEY_R_DIR, | ||
| + stringProperty( KEY_DEF_DELIM_BEGAN ).get(), | ||
| + stringProperty( KEY_DEF_DELIM_ENDED ).get() ) ), | ||
| + Setting.of( title( KEY_R_DIR ), | ||
| + fileProperty( KEY_R_DIR ), true ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_R_SCRIPT ), | ||
| + Setting.of( label( KEY_R_SCRIPT ) ), | ||
| + createMultilineSetting( "Script", KEY_R_SCRIPT ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_R_DELIM_BEGAN ), | ||
| + Setting.of( label( KEY_R_DELIM_BEGAN ) ), | ||
| + Setting.of( title( KEY_R_DELIM_BEGAN ), | ||
| + stringProperty( KEY_R_DELIM_BEGAN ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_R_DELIM_ENDED ), | ||
| + Setting.of( label( KEY_R_DELIM_ENDED ) ), | ||
| + Setting.of( title( KEY_R_DELIM_ENDED ), | ||
| + stringProperty( KEY_R_DELIM_ENDED ) ) | ||
| + ) | ||
| + ), | ||
| + Category.of( | ||
| + get( KEY_IMAGES ), | ||
| + Group.of( | ||
| + get( KEY_IMAGES_DIR ), | ||
| + Setting.of( label( KEY_IMAGES_DIR ) ), | ||
| + Setting.of( title( KEY_IMAGES_DIR ), | ||
| + fileProperty( KEY_IMAGES_DIR ), true ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_IMAGES_ORDER ), | ||
| + Setting.of( label( KEY_IMAGES_ORDER ) ), | ||
| + Setting.of( title( KEY_IMAGES_ORDER ), | ||
| + stringProperty( KEY_IMAGES_ORDER ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_IMAGES_RESIZE ), | ||
| + Setting.of( label( KEY_IMAGES_RESIZE ) ), | ||
| + Setting.of( title( KEY_IMAGES_RESIZE ), | ||
| + booleanProperty( KEY_IMAGES_RESIZE ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_IMAGES_SERVER ), | ||
| + Setting.of( label( KEY_IMAGES_SERVER ) ), | ||
| + Setting.of( title( KEY_IMAGES_SERVER ), | ||
| + stringProperty( KEY_IMAGES_SERVER ) ) | ||
| + ) | ||
| + ), | ||
| + Category.of( | ||
| + get( KEY_DEF ), | ||
| + Group.of( | ||
| + get( KEY_DEF_PATH ), | ||
| + Setting.of( label( KEY_DEF_PATH ) ), | ||
| + Setting.of( title( KEY_DEF_PATH ), | ||
| + fileProperty( KEY_DEF_PATH ), false ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_DEF_DELIM_BEGAN ), | ||
| + Setting.of( label( KEY_DEF_DELIM_BEGAN ) ), | ||
| + Setting.of( title( KEY_DEF_DELIM_BEGAN ), | ||
| + stringProperty( KEY_DEF_DELIM_BEGAN ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_DEF_DELIM_ENDED ), | ||
| + Setting.of( label( KEY_DEF_DELIM_ENDED ) ), | ||
| + Setting.of( title( KEY_DEF_DELIM_ENDED ), | ||
| + stringProperty( KEY_DEF_DELIM_ENDED ) ) | ||
| + ) | ||
| + ), | ||
| + Category.of( | ||
| + get( KEY_UI_FONT ), | ||
| + Group.of( | ||
| + get( KEY_UI_FONT_EDITOR ), | ||
| + Setting.of( label( KEY_UI_FONT_EDITOR_NAME ) ), | ||
| + Setting.of( title( KEY_UI_FONT_EDITOR_NAME ), | ||
| + createFontNameField( | ||
| + stringProperty( KEY_UI_FONT_EDITOR_NAME ), | ||
| + doubleProperty( KEY_UI_FONT_EDITOR_SIZE ) ), | ||
| + stringProperty( KEY_UI_FONT_EDITOR_NAME ) ), | ||
| + Setting.of( label( KEY_UI_FONT_EDITOR_SIZE ) ), | ||
| + Setting.of( title( KEY_UI_FONT_EDITOR_SIZE ), | ||
| + doubleProperty( KEY_UI_FONT_EDITOR_SIZE ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_UI_FONT_PREVIEW ), | ||
| + Setting.of( label( KEY_UI_FONT_PREVIEW_NAME ) ), | ||
| + Setting.of( title( KEY_UI_FONT_PREVIEW_NAME ), | ||
| + createFontNameField( | ||
| + stringProperty( KEY_UI_FONT_PREVIEW_NAME ), | ||
| + doubleProperty( KEY_UI_FONT_PREVIEW_SIZE ) ), | ||
| + stringProperty( KEY_UI_FONT_PREVIEW_NAME ) ), | ||
| + Setting.of( label( KEY_UI_FONT_PREVIEW_SIZE ) ), | ||
| + Setting.of( title( KEY_UI_FONT_PREVIEW_SIZE ), | ||
| + doubleProperty( KEY_UI_FONT_PREVIEW_SIZE ) ), | ||
| + Setting.of( label( KEY_UI_FONT_PREVIEW_MONO_NAME ) ), | ||
| + Setting.of( title( KEY_UI_FONT_PREVIEW_MONO_NAME ), | ||
| + createFontNameField( | ||
| + stringProperty( KEY_UI_FONT_PREVIEW_MONO_NAME ), | ||
| + doubleProperty( KEY_UI_FONT_PREVIEW_MONO_SIZE ) ), | ||
| + stringProperty( KEY_UI_FONT_PREVIEW_MONO_NAME ) ), | ||
| + Setting.of( label( KEY_UI_FONT_PREVIEW_MONO_SIZE ) ), | ||
| + Setting.of( title( KEY_UI_FONT_PREVIEW_MONO_SIZE ), | ||
| + doubleProperty( KEY_UI_FONT_PREVIEW_MONO_SIZE ) ) | ||
| + ) | ||
| + ), | ||
| + Category.of( | ||
| + get( KEY_UI_SKIN ), | ||
| + Group.of( | ||
| + get( KEY_UI_SKIN_SELECTION ), | ||
| + Setting.of( label( KEY_UI_SKIN_SELECTION ) ), | ||
| + Setting.of( title( KEY_UI_SKIN_SELECTION ), | ||
| + skinListProperty(), | ||
| + skinProperty( KEY_UI_SKIN_SELECTION ) ) | ||
| + ), | ||
| + Group.of( | ||
| + get( KEY_UI_SKIN_CUSTOM ), | ||
| + Setting.of( label( KEY_UI_SKIN_CUSTOM ) ), | ||
| + Setting.of( title( KEY_UI_SKIN_CUSTOM ), | ||
| + fileProperty( KEY_UI_SKIN_CUSTOM ), false ) | ||
| + ) | ||
| + ), | ||
| + Category.of( | ||
| + get( KEY_UI_PREVIEW ), | ||
| + Group.of( | ||
| + get( KEY_UI_PREVIEW_STYLESHEET ), | ||
| + Setting.of( label( KEY_UI_PREVIEW_STYLESHEET ) ), | ||
| + Setting.of( title( KEY_UI_PREVIEW_STYLESHEET ), | ||
| + fileProperty( KEY_UI_PREVIEW_STYLESHEET ), false ) | ||
| + ) | ||
| + ), | ||
| + Category.of( | ||
| + get( KEY_LANGUAGE ), | ||
| + Group.of( | ||
| + get( KEY_LANGUAGE_LOCALE ), | ||
| + Setting.of( label( KEY_LANGUAGE_LOCALE ) ), | ||
| + Setting.of( title( KEY_LANGUAGE_LOCALE ), | ||
| + localeListProperty(), | ||
| + localeProperty( KEY_LANGUAGE_LOCALE ) ) | ||
| + ) | ||
| + )}; | ||
| + } | ||
| + | ||
| + @SuppressWarnings( "unchecked" ) | ||
| + private Setting<StringField, StringProperty> createMultilineSetting( | ||
| + final String description, final Key property ) { | ||
| + final Setting<StringField, StringProperty> setting = | ||
| + Setting.of( description, stringProperty( property ) ); | ||
| + final var field = setting.getElement(); | ||
| + field.multiline( true ); | ||
| + | ||
| + return setting; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Map ENTER and ESCAPE keys to OK and CANCEL buttons, respectively. | ||
| + */ | ||
| + private void initKeyEventHandler( final PreferencesFx preferences ) { | ||
| + final var view = preferences.getView(); | ||
| + final var nodes = view.getChildrenUnmodifiable(); | ||
| + final var master = (MasterDetailPane) nodes.get( 0 ); | ||
| + final var detail = (NavigationView) master.getDetailNode(); | ||
| + final var pane = (DialogPane) view.getParent(); | ||
| + | ||
| + detail.setOnKeyReleased( key -> { | ||
| + switch( key.getCode() ) { | ||
| + case ENTER -> ((Button) pane.lookupButton( OK )).fire(); | ||
| + case ESCAPE -> ((Button) pane.lookupButton( CANCEL )).fire(); | ||
| + } | ||
| + } ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates a label for the given key after interpolating its value. | ||
| + * | ||
| + * @param key The key to find in the resource bundle. | ||
| + * @return The value of the key as a label. | ||
| + */ | ||
| + private Node label( final Key key ) { | ||
| + return label( key, (String[]) null ); | ||
| + } | ||
| + | ||
| + private Node label( final Key key, final String... values ) { | ||
| + return new Label( get( key.toString() + ".desc", (Object[]) values ) ); | ||
| + } | ||
| + | ||
| + private String title( final Key key ) { | ||
| + return get( key.toString() + ".title" ); | ||
| + } | ||
| + | ||
| + private ObjectProperty<File> fileProperty( final Key key ) { | ||
| + return mWorkspace.fileProperty( key ); | ||
| + } | ||
| + | ||
| + private StringProperty stringProperty( final Key key ) { | ||
| + return mWorkspace.stringProperty( key ); | ||
| + } | ||
| + | ||
| + private BooleanProperty booleanProperty( final Key key ) { | ||
| + return mWorkspace.booleanProperty( key ); | ||
| + } | ||
| + | ||
| + @SuppressWarnings( "SameParameterValue" ) | ||
| + private IntegerProperty integerProperty( final Key key ) { | ||
| + return mWorkspace.integerProperty( key ); | ||
| + } | ||
| + | ||
| + @SuppressWarnings( "SameParameterValue" ) | ||
| + private DoubleProperty doubleProperty( final Key key ) { | ||
| + return mWorkspace.doubleProperty( key ); | ||
| + } | ||
| + | ||
| + private ObjectProperty<String> skinProperty( final Key key ) { | ||
| + return mWorkspace.skinProperty( key ); | ||
| + } | ||
| + | ||
| + private ObjectProperty<String> localeProperty( final Key key ) { | ||
| + return mWorkspace.localeProperty( key ); | ||
| + } | ||
| + | ||
| + private <E> ListProperty<E> listEntryProperty( final Key key ) { | ||
| + return mWorkspace.listsProperty( key ); | ||
| } | ||
| import com.dlsc.formsfx.model.util.BindingMode; | ||
| import javafx.beans.property.ListProperty; | ||
| +import javafx.beans.property.Property; | ||
| import javafx.beans.property.SimpleListProperty; | ||
| import java.util.ArrayList; | ||
| +import static com.dlsc.formsfx.model.util.BindingMode.CONTINUOUS; | ||
| import static javafx.collections.FXCollections.observableList; | ||
| /** | ||
| * Responsible for binding a form field to a map of values that, ultimately, | ||
| * users may edit. | ||
| * | ||
| - * @param <E> The type of elements to store in the list. | ||
| + * @param <P> The type of {@link Property} to store in the list. | ||
| */ | ||
| -public class TableField<E> extends Field<TableField<E>> { | ||
| +public class TableField<P> extends Field<TableField<P>> { | ||
| /** | ||
| * Create a writeable list as the data model. | ||
| */ | ||
| - private final ListProperty<E> mViewProperty = new SimpleListProperty<>( | ||
| + private final ListProperty<P> mViewProperty = new SimpleListProperty<>( | ||
| observableList( new ArrayList<>() ) | ||
| ); | ||
| - public static <E> TableField<E> ofListType() { | ||
| - return new TableField<>(); | ||
| + /** | ||
| + * Contains the data model entries to persist. | ||
| + */ | ||
| + private final ListProperty<P> mSaveProperty; | ||
| + | ||
| + /** | ||
| + * Creates a new {@link TableField} with a reference to the list that is to | ||
| + * be persisted. | ||
| + * | ||
| + * @param persist A list of items that will be persisted. | ||
| + * @param <P> The type of elements in the list to persist. | ||
| + * @return A new {@link TableField} used to help render a UI widget. | ||
| + */ | ||
| + public static <P> TableField<P> ofListType( final ListProperty<P> persist ) { | ||
| + return new TableField<>( persist ); | ||
| } | ||
| - private TableField() { | ||
| + private TableField( final ListProperty<P> property ) { | ||
| + mSaveProperty = property; | ||
| } | ||
| - public ListProperty<E> viewProperty() { | ||
| + /** | ||
| + * Returns the data model that seeds the user interface. At any point the | ||
| + * user may cancel editing, which will revert to the previously persisted | ||
| + * set. | ||
| + * | ||
| + * @return The source for values displayed in the UI. | ||
| + */ | ||
| + public ListProperty<P> viewProperty() { | ||
| return mViewProperty; | ||
| } | ||
| + /** | ||
| + * Called when a new UI instance is opened. | ||
| + * | ||
| + * @param bindingMode Indicates how the view data model is bound to the | ||
| + * persistence data model. | ||
| + */ | ||
| @Override | ||
| public void setBindingMode( final BindingMode bindingMode ) { | ||
| - System.out.println( "BIND TO: " + bindingMode ); | ||
| + if( CONTINUOUS.equals( bindingMode ) ) { | ||
| + mViewProperty.get().addAll( mSaveProperty.get() ); | ||
| + } | ||
| } | ||
| } | ||
| + /** | ||
| + * Update the properties to save by copying the properties updated in the | ||
| + * user interface (i.e., the view). | ||
| + */ | ||
| @Override | ||
| public void persist() { | ||
| - System.out.println( "PURSIST: " + mViewProperty ); | ||
| + mSaveProperty.get().addAll( mViewProperty.get() ); | ||
| } | ||
| + /** | ||
| + * The {@link TableField} doesn't bind values, as such the reset can be | ||
| + * a no-op because only {@link #persist()} will update the properties to | ||
| + * save. | ||
| + */ | ||
| @Override | ||
| - public void reset() { | ||
| - System.out.println( "RESET" ); | ||
| - } | ||
| + public void reset() {} | ||
| } | ||
| KEY_UI_FILES_PATH, | ||
| createSetProperty( new HashSet<String>() ) | ||
| - ), | ||
| - entry( | ||
| - KEY_DOC_META, | ||
| - createSetProperty( new HashSet<Entry<String, String>>() ) | ||
| - ) | ||
| - ); | ||
| - | ||
| - /** | ||
| - * Creates a new {@link Workspace} that will attempt to load a configuration | ||
| - * file. If the configuration file cannot be loaded, the workspace settings | ||
| - * will return default values. This allows unit tests to provide an instance | ||
| - * of {@link Workspace} when necessary without encountering failures. | ||
| - */ | ||
| - public Workspace() { | ||
| - load( FILE_PREFERENCES ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Creates a new {@link Workspace} that will attempt to load the given | ||
| - * configuration file. | ||
| - * | ||
| - * @param filename The file to load. | ||
| - */ | ||
| - public Workspace( final String filename ) { | ||
| - load( filename ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns a value that represents a setting in the application that the user | ||
| - * may configure, either directly or indirectly. | ||
| - * | ||
| - * @param key The reference to the users' preference stored in deference | ||
| - * of app reëntrance. | ||
| - * @return An observable property to be persisted. | ||
| - */ | ||
| - @SuppressWarnings( "unchecked" ) | ||
| - public <T, U extends Property<T>> U valuesProperty( final Key key ) { | ||
| - assert key != null; | ||
| - // The type that goes into the map must come out. | ||
| - return (U) VALUES.get( key ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns a list of values that represent a setting in the application that | ||
| - * the user may configure, either directly or indirectly. The property | ||
| - * returned is backed by a mutable {@link Set}. | ||
| - * | ||
| - * @param key The {@link Key} associated with a preference value. | ||
| - * @return An observable property to be persisted. | ||
| - */ | ||
| - @SuppressWarnings( "unchecked" ) | ||
| - public <T> SetProperty<T> setsProperty( final Key key ) { | ||
| - assert key != null; | ||
| - // The type that goes into the map must come out. | ||
| - return (SetProperty<T>) SETS.get( key ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Creates an instance of {@link ObservableList} that is based on a | ||
| - * modifiable observable array list for the given items. | ||
| - * | ||
| - * @param items The items to wrap in an observable list. | ||
| - * @param <E> The type of items to add to the list. | ||
| - * @return An observable property that can have its contents modified. | ||
| - */ | ||
| - public static <E> ObservableList<E> listProperty( final Set<E> items ) { | ||
| - return new SimpleListProperty<>( observableArrayList( items ) ); | ||
| - } | ||
| - | ||
| - private static <E> SetProperty<E> createSetProperty( final Set<E> set ) { | ||
| - return new SimpleSetProperty<>( observableSet( set ) ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * @param value Default value. | ||
| - */ | ||
| - private static StringProperty asStringProperty( final String value ) { | ||
| - return new SimpleStringProperty( value ); | ||
| - } | ||
| - | ||
| - private static BooleanProperty asBooleanProperty() { | ||
| - return new SimpleBooleanProperty(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * @param value Default value. | ||
| - */ | ||
| - @SuppressWarnings( "SameParameterValue" ) | ||
| - private static BooleanProperty asBooleanProperty( final boolean value ) { | ||
| - return new SimpleBooleanProperty( value ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * @param value Default value. | ||
| - */ | ||
| - @SuppressWarnings( "SameParameterValue" ) | ||
| - private static IntegerProperty asIntegerProperty( final int value ) { | ||
| - return new SimpleIntegerProperty( value ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * @param value Default value. | ||
| - */ | ||
| - private static DoubleProperty asDoubleProperty( final double value ) { | ||
| - return new SimpleDoubleProperty( value ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * @param value Default value. | ||
| - */ | ||
| - private static FileProperty asFileProperty( final File value ) { | ||
| - return new FileProperty( value ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * @param value Default value. | ||
| - */ | ||
| - @SuppressWarnings( "SameParameterValue" ) | ||
| - private static LocaleProperty asLocaleProperty( final Locale value ) { | ||
| - return new LocaleProperty( value ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * @param value Default value. | ||
| - */ | ||
| - @SuppressWarnings( "SameParameterValue" ) | ||
| - private static SkinProperty asSkinProperty( final String value ) { | ||
| - return new SkinProperty( value ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the {@link String} {@link Property} associated with the given | ||
| - * {@link Key} from the internal list of preference values. The caller | ||
| - * must be sure that the given {@link Key} is associated with a {@link File} | ||
| - * {@link Property}. | ||
| - * | ||
| - * @param key The {@link Key} associated with a preference value. | ||
| - * @return The value associated with the given {@link Key}. | ||
| - */ | ||
| - public StringProperty stringProperty( final Key key ) { | ||
| - assert key != null; | ||
| - return valuesProperty( key ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the {@link Boolean} {@link Property} associated with the given | ||
| - * {@link Key} from the internal list of preference values. The caller | ||
| - * must be sure that the given {@link Key} is associated with a {@link File} | ||
| - * {@link Property}. | ||
| - * | ||
| - * @param key The {@link Key} associated with a preference value. | ||
| - * @return The value associated with the given {@link Key}. | ||
| - */ | ||
| - public BooleanProperty booleanProperty( final Key key ) { | ||
| - assert key != null; | ||
| - return valuesProperty( key ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the {@link Integer} {@link Property} associated with the given | ||
| - * {@link Key} from the internal list of preference values. The caller | ||
| - * must be sure that the given {@link Key} is associated with a {@link File} | ||
| - * {@link Property}. | ||
| - * | ||
| - * @param key The {@link Key} associated with a preference value. | ||
| - * @return The value associated with the given {@link Key}. | ||
| - */ | ||
| - public IntegerProperty integerProperty( final Key key ) { | ||
| - assert key != null; | ||
| - return valuesProperty( key ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the {@link Double} {@link Property} associated with the given | ||
| - * {@link Key} from the internal list of preference values. The caller | ||
| - * must be sure that the given {@link Key} is associated with a {@link File} | ||
| - * {@link Property}. | ||
| - * | ||
| - * @param key The {@link Key} associated with a preference value. | ||
| - * @return The value associated with the given {@link Key}. | ||
| - */ | ||
| - public DoubleProperty doubleProperty( final Key key ) { | ||
| - assert key != null; | ||
| - return valuesProperty( key ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the {@link File} {@link Property} associated with the given | ||
| - * {@link Key} from the internal list of preference values. The caller | ||
| - * must be sure that the given {@link Key} is associated with a {@link File} | ||
| - * {@link Property}. | ||
| - * | ||
| - * @param key The {@link Key} associated with a preference value. | ||
| - * @return The value associated with the given {@link Key}. | ||
| - */ | ||
| - public ObjectProperty<File> fileProperty( final Key key ) { | ||
| - assert key != null; | ||
| - return valuesProperty( key ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the {@link Locale} {@link Property} associated with the given | ||
| - * {@link Key} from the internal list of preference values. The caller | ||
| - * must be sure that the given {@link Key} is associated with a {@link File} | ||
| - * {@link Property}. | ||
| - * | ||
| - * @param key The {@link Key} associated with a preference value. | ||
| - * @return The value associated with the given {@link Key}. | ||
| - */ | ||
| - public LocaleProperty localeProperty( final Key key ) { | ||
| - assert key != null; | ||
| - return valuesProperty( key ); | ||
| - } | ||
| - | ||
| - public ObjectProperty<String> skinProperty( final Key key ) { | ||
| - assert key != null; | ||
| - return valuesProperty( key ); | ||
| - } | ||
| - | ||
| - @Override | ||
| - public String getString( final Key key ) { | ||
| - assert key != null; | ||
| - return stringProperty( key ).get(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the {@link Boolean} preference value associated with the given | ||
| - * {@link Key}. The caller must be sure that the given {@link Key} is | ||
| - * associated with a value that matches the return type. | ||
| - * | ||
| - * @param key The {@link Key} associated with a preference value. | ||
| - * @return The value associated with the given {@link Key}. | ||
| - */ | ||
| - @Override | ||
| - public boolean getBoolean( final Key key ) { | ||
| - assert key != null; | ||
| - return booleanProperty( key ).get(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the {@link Integer} preference value associated with the given | ||
| - * {@link Key}. The caller must be sure that the given {@link Key} is | ||
| - * associated with a value that matches the return type. | ||
| - * | ||
| - * @param key The {@link Key} associated with a preference value. | ||
| - * @return The value associated with the given {@link Key}. | ||
| - */ | ||
| - @Override | ||
| - public int getInteger( final Key key ) { | ||
| - assert key != null; | ||
| - return integerProperty( key ).get(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the {@link Double} preference value associated with the given | ||
| - * {@link Key}. The caller must be sure that the given {@link Key} is | ||
| - * associated with a value that matches the return type. | ||
| - * | ||
| - * @param key The {@link Key} associated with a preference value. | ||
| - * @return The value associated with the given {@link Key}. | ||
| - */ | ||
| - @Override | ||
| - public double getDouble( final Key key ) { | ||
| - assert key != null; | ||
| - return doubleProperty( key ).get(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the {@link File} preference value associated with the given | ||
| - * {@link Key}. The caller must be sure that the given {@link Key} is | ||
| - * associated with a value that matches the return type. | ||
| - * | ||
| - * @param key The {@link Key} associated with a preference value. | ||
| - * @return The value associated with the given {@link Key}. | ||
| - */ | ||
| - @Override | ||
| - public File getFile( final Key key ) { | ||
| - assert key != null; | ||
| - return fileProperty( key ).get(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the language locale setting for the | ||
| - * {@link AppKeys#KEY_LANGUAGE_LOCALE} key. | ||
| - * | ||
| - * @return The user's current locale setting. | ||
| - */ | ||
| - public Locale getLocale() { | ||
| - return localeProperty( KEY_LANGUAGE_LOCALE ).toLocale(); | ||
| - } | ||
| - | ||
| - private Sigils createSigils( final Key keyBegan, final Key keyEnded ) { | ||
| - assert keyBegan != null; | ||
| - assert keyEnded != null; | ||
| - | ||
| - return new Sigils( getString( keyBegan ), getString( keyEnded ) ); | ||
| - } | ||
| - | ||
| - public SigilOperator createYamlSigilOperator() { | ||
| - return new YamlSigilOperator( | ||
| - createSigils( KEY_DEF_DELIM_BEGAN, KEY_DEF_DELIM_ENDED ) | ||
| - ); | ||
| - } | ||
| - | ||
| - public SigilOperator createRSigilOperator() { | ||
| - return new RSigilOperator( | ||
| - createSigils( KEY_R_DELIM_BEGAN, KEY_R_DELIM_ENDED ), | ||
| - createYamlSigilOperator() | ||
| - ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Delegates to {@link #listen(Key, ReadOnlyProperty, BooleanSupplier)}, | ||
| - * providing a value of {@code true} for the {@link BooleanSupplier} to | ||
| - * indicate the property changes always take effect. | ||
| - * | ||
| - * @param key The value to bind to the internal key property. | ||
| - * @param property The external property value that sets the internal value. | ||
| - */ | ||
| - public <T> void listen( final Key key, final ReadOnlyProperty<T> property ) { | ||
| - listen( key, property, () -> true ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Binds a read-only property to a value in the preferences. This allows | ||
| - * user interface properties to change and the preferences will be | ||
| - * synchronized automatically. | ||
| - * <p> | ||
| - * This calls {@link Platform#runLater(Runnable)} to ensure that all pending | ||
| - * application window states are finished before assessing whether property | ||
| - * changes should be applied. Without this, exiting the application while the | ||
| - * window is maximized would persist the window's maximum dimensions, | ||
| - * preventing restoration to its prior, non-maximum size. | ||
| - * </p> | ||
| - * | ||
| - * @param key The value to bind to the internal key property. | ||
| - * @param property The external property value that sets the internal value. | ||
| - * @param enabled Indicates whether property changes should be applied. | ||
| - */ | ||
| - public <T> void listen( | ||
| - final Key key, | ||
| - final ReadOnlyProperty<T> property, | ||
| - final BooleanSupplier enabled ) { | ||
| - property.addListener( | ||
| - ( c, o, n ) -> runLater( () -> { | ||
| - if( enabled.getAsBoolean() ) { | ||
| - valuesProperty( key ).setValue( n ); | ||
| - } | ||
| - } ) | ||
| - ); | ||
| - } | ||
| - | ||
| - public void loadValueKeys( final Consumer<Key> consumer ) { | ||
| - VALUES.keySet().forEach( consumer ); | ||
| - } | ||
| - | ||
| - public void loadSetKeys( final Consumer<Key> consumer ) { | ||
| - SETS.keySet().forEach( consumer ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Calls the given consumer for all single-value keys. For lists, see | ||
| - * {@link #saveSets(BiConsumer)}. | ||
| - * | ||
| - * @param consumer Called to accept each preference key value. | ||
| - */ | ||
| - public void saveValues( final BiConsumer<Key, Property<?>> consumer ) { | ||
| - VALUES.forEach( consumer ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Calls the given consumer for all multi-value keys. For single items, see | ||
| - * {@link #saveValues(BiConsumer)}. Callers are responsible for iterating | ||
| - * over the list of items retrieved through this method. | ||
| - * | ||
| - * @param consumer Called to accept each preference key list. | ||
| - */ | ||
| - public void saveSets( final BiConsumer<Key, SetProperty<?>> consumer ) { | ||
| - SETS.forEach( consumer ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Saves the current workspace. | ||
| - */ | ||
| - public void save() { | ||
| - try { | ||
| - final var config = new XMLConfiguration(); | ||
| - | ||
| - // The root config key can only be set for an empty configuration file. | ||
| - config.setRootElementName( APP_TITLE_LOWERCASE ); | ||
| - valuesProperty( KEY_META_VERSION ).setValue( getVersion() ); | ||
| - | ||
| - saveValues( | ||
| - ( key, property ) -> | ||
| - config.setProperty( key.toString(), marshall( property ) ) | ||
| - ); | ||
| - | ||
| - saveSets( | ||
| - ( key, set ) -> { | ||
| - final var keyName = key.toString(); | ||
| - set.forEach( value -> config.addProperty( keyName, value ) ); | ||
| - } | ||
| - ); | ||
| - | ||
| - new FileHandler( config ).save( FILE_PREFERENCES ); | ||
| - } catch( final Exception ex ) { | ||
| - clue( ex ); | ||
| - } | ||
| - } | ||
| - | ||
| - /** | ||
| - * Attempts to load the {@link Constants#FILE_PREFERENCES} configuration file. | ||
| - * If not found, this will fall back to an empty configuration file, leaving | ||
| - * the application to fill in default values. | ||
| - * | ||
| - * @param filename The file containing user preferences to load. | ||
| - */ | ||
| - private void load( final String filename ) { | ||
| - try { | ||
| - final var config = new Configurations().xml( filename ); | ||
| - | ||
| - loadValueKeys( key -> { | ||
| - final var configValue = config.getProperty( key.toString() ); | ||
| - | ||
| - // Allow other properties to load, even if any are missing. | ||
| - if( configValue != null ) { | ||
| - final var propertyValue = valuesProperty( key ); | ||
| - propertyValue.setValue( unmarshall( propertyValue, configValue ) ); | ||
| - } | ||
| - } ); | ||
| - | ||
| - loadSetKeys( key -> { | ||
| - final var configSet = | ||
| - new LinkedHashSet<>( config.getList( key.toString() ) ); | ||
| - final var propertySet = setsProperty( key ); | ||
| - propertySet.setValue( observableSet( configSet ) ); | ||
| + ) | ||
| + ); | ||
| + | ||
| + private final Map<Key, ListProperty<?>> LISTS = Map.ofEntries( | ||
| + entry( | ||
| + KEY_DOC_META, | ||
| + createListProperty( new LinkedList<Entry<String, String>>() ) | ||
| + ) | ||
| + ); | ||
| + | ||
| + /** | ||
| + * Creates a new {@link Workspace} that will attempt to load a configuration | ||
| + * file. If the configuration file cannot be loaded, the workspace settings | ||
| + * will return default values. This allows unit tests to provide an instance | ||
| + * of {@link Workspace} when necessary without encountering failures. | ||
| + */ | ||
| + public Workspace() { | ||
| + load( FILE_PREFERENCES ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates a new {@link Workspace} that will attempt to load the given | ||
| + * configuration file. | ||
| + * | ||
| + * @param filename The file to load. | ||
| + */ | ||
| + public Workspace( final String filename ) { | ||
| + load( filename ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns a value that represents a setting in the application that the user | ||
| + * may configure, either directly or indirectly. | ||
| + * | ||
| + * @param key The reference to the users' preference stored in deference | ||
| + * of app reëntrance. | ||
| + * @return An observable property to be persisted. | ||
| + */ | ||
| + @SuppressWarnings( "unchecked" ) | ||
| + public <T, U extends Property<T>> U valuesProperty( final Key key ) { | ||
| + assert key != null; | ||
| + // The type that goes into the map must come out. | ||
| + return (U) VALUES.get( key ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns a set of values that represent a setting in the application that | ||
| + * the user may configure, either directly or indirectly. The property | ||
| + * returned is backed by a {@link Set}. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return An observable property to be persisted. | ||
| + */ | ||
| + @SuppressWarnings( "unchecked" ) | ||
| + public <T> SetProperty<T> setsProperty( final Key key ) { | ||
| + assert key != null; | ||
| + // The type that goes into the map must come out. | ||
| + return (SetProperty<T>) SETS.get( key ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns a list of values that represent a setting in the application that | ||
| + * the user may configure, either directly or indirectly. The property | ||
| + * returned is backed by a mutable {@link List}. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return An observable property to be persisted. | ||
| + */ | ||
| + @SuppressWarnings( "unchecked" ) | ||
| + public <T> ListProperty<T> listsProperty( final Key key ) { | ||
| + assert key != null; | ||
| + return (ListProperty<T>) LISTS.get( key ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates an instance of {@link ObservableList} that is based on a | ||
| + * modifiable observable array list for the given items. | ||
| + * | ||
| + * @param items The items to wrap in an observable list. | ||
| + * @param <E> The type of items to add to the list. | ||
| + * @return An observable property that can have its contents modified. | ||
| + */ | ||
| + public static <E> ObservableList<E> listProperty( final Set<E> items ) { | ||
| + return new SimpleListProperty<>( observableArrayList( items ) ); | ||
| + } | ||
| + | ||
| + private static <E> SetProperty<E> createSetProperty( final Set<E> set ) { | ||
| + return new SimpleSetProperty<>( observableSet( set ) ); | ||
| + } | ||
| + | ||
| + private static <E> ListProperty<E> createListProperty( final List<E> list ) { | ||
| + return new SimpleListProperty<>( observableArrayList( list ) ); | ||
| + | ||
| + } | ||
| + | ||
| + /** | ||
| + * @param value Default value. | ||
| + */ | ||
| + private static StringProperty asStringProperty( final String value ) { | ||
| + return new SimpleStringProperty( value ); | ||
| + } | ||
| + | ||
| + private static BooleanProperty asBooleanProperty() { | ||
| + return new SimpleBooleanProperty(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * @param value Default value. | ||
| + */ | ||
| + @SuppressWarnings( "SameParameterValue" ) | ||
| + private static BooleanProperty asBooleanProperty( final boolean value ) { | ||
| + return new SimpleBooleanProperty( value ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * @param value Default value. | ||
| + */ | ||
| + @SuppressWarnings( "SameParameterValue" ) | ||
| + private static IntegerProperty asIntegerProperty( final int value ) { | ||
| + return new SimpleIntegerProperty( value ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * @param value Default value. | ||
| + */ | ||
| + private static DoubleProperty asDoubleProperty( final double value ) { | ||
| + return new SimpleDoubleProperty( value ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * @param value Default value. | ||
| + */ | ||
| + private static FileProperty asFileProperty( final File value ) { | ||
| + return new FileProperty( value ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * @param value Default value. | ||
| + */ | ||
| + @SuppressWarnings( "SameParameterValue" ) | ||
| + private static LocaleProperty asLocaleProperty( final Locale value ) { | ||
| + return new LocaleProperty( value ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * @param value Default value. | ||
| + */ | ||
| + @SuppressWarnings( "SameParameterValue" ) | ||
| + private static SkinProperty asSkinProperty( final String value ) { | ||
| + return new SkinProperty( value ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the {@link String} {@link Property} associated with the given | ||
| + * {@link Key} from the internal list of preference values. The caller | ||
| + * must be sure that the given {@link Key} is associated with a {@link File} | ||
| + * {@link Property}. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return The value associated with the given {@link Key}. | ||
| + */ | ||
| + public StringProperty stringProperty( final Key key ) { | ||
| + assert key != null; | ||
| + return valuesProperty( key ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the {@link Boolean} {@link Property} associated with the given | ||
| + * {@link Key} from the internal list of preference values. The caller | ||
| + * must be sure that the given {@link Key} is associated with a {@link File} | ||
| + * {@link Property}. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return The value associated with the given {@link Key}. | ||
| + */ | ||
| + public BooleanProperty booleanProperty( final Key key ) { | ||
| + assert key != null; | ||
| + return valuesProperty( key ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the {@link Integer} {@link Property} associated with the given | ||
| + * {@link Key} from the internal list of preference values. The caller | ||
| + * must be sure that the given {@link Key} is associated with a {@link File} | ||
| + * {@link Property}. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return The value associated with the given {@link Key}. | ||
| + */ | ||
| + public IntegerProperty integerProperty( final Key key ) { | ||
| + assert key != null; | ||
| + return valuesProperty( key ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the {@link Double} {@link Property} associated with the given | ||
| + * {@link Key} from the internal list of preference values. The caller | ||
| + * must be sure that the given {@link Key} is associated with a {@link File} | ||
| + * {@link Property}. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return The value associated with the given {@link Key}. | ||
| + */ | ||
| + public DoubleProperty doubleProperty( final Key key ) { | ||
| + assert key != null; | ||
| + return valuesProperty( key ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the {@link File} {@link Property} associated with the given | ||
| + * {@link Key} from the internal list of preference values. The caller | ||
| + * must be sure that the given {@link Key} is associated with a {@link File} | ||
| + * {@link Property}. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return The value associated with the given {@link Key}. | ||
| + */ | ||
| + public ObjectProperty<File> fileProperty( final Key key ) { | ||
| + assert key != null; | ||
| + return valuesProperty( key ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the {@link Locale} {@link Property} associated with the given | ||
| + * {@link Key} from the internal list of preference values. The caller | ||
| + * must be sure that the given {@link Key} is associated with a {@link File} | ||
| + * {@link Property}. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return The value associated with the given {@link Key}. | ||
| + */ | ||
| + public LocaleProperty localeProperty( final Key key ) { | ||
| + assert key != null; | ||
| + return valuesProperty( key ); | ||
| + } | ||
| + | ||
| + public ObjectProperty<String> skinProperty( final Key key ) { | ||
| + assert key != null; | ||
| + return valuesProperty( key ); | ||
| + } | ||
| + | ||
| + @Override | ||
| + public String getString( final Key key ) { | ||
| + assert key != null; | ||
| + return stringProperty( key ).get(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the {@link Boolean} preference value associated with the given | ||
| + * {@link Key}. The caller must be sure that the given {@link Key} is | ||
| + * associated with a value that matches the return type. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return The value associated with the given {@link Key}. | ||
| + */ | ||
| + @Override | ||
| + public boolean getBoolean( final Key key ) { | ||
| + assert key != null; | ||
| + return booleanProperty( key ).get(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the {@link Integer} preference value associated with the given | ||
| + * {@link Key}. The caller must be sure that the given {@link Key} is | ||
| + * associated with a value that matches the return type. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return The value associated with the given {@link Key}. | ||
| + */ | ||
| + @Override | ||
| + public int getInteger( final Key key ) { | ||
| + assert key != null; | ||
| + return integerProperty( key ).get(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the {@link Double} preference value associated with the given | ||
| + * {@link Key}. The caller must be sure that the given {@link Key} is | ||
| + * associated with a value that matches the return type. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return The value associated with the given {@link Key}. | ||
| + */ | ||
| + @Override | ||
| + public double getDouble( final Key key ) { | ||
| + assert key != null; | ||
| + return doubleProperty( key ).get(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the {@link File} preference value associated with the given | ||
| + * {@link Key}. The caller must be sure that the given {@link Key} is | ||
| + * associated with a value that matches the return type. | ||
| + * | ||
| + * @param key The {@link Key} associated with a preference value. | ||
| + * @return The value associated with the given {@link Key}. | ||
| + */ | ||
| + @Override | ||
| + public File getFile( final Key key ) { | ||
| + assert key != null; | ||
| + return fileProperty( key ).get(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the language locale setting for the | ||
| + * {@link AppKeys#KEY_LANGUAGE_LOCALE} key. | ||
| + * | ||
| + * @return The user's current locale setting. | ||
| + */ | ||
| + public Locale getLocale() { | ||
| + return localeProperty( KEY_LANGUAGE_LOCALE ).toLocale(); | ||
| + } | ||
| + | ||
| + private Sigils createSigils( final Key keyBegan, final Key keyEnded ) { | ||
| + assert keyBegan != null; | ||
| + assert keyEnded != null; | ||
| + | ||
| + return new Sigils( getString( keyBegan ), getString( keyEnded ) ); | ||
| + } | ||
| + | ||
| + public SigilOperator createYamlSigilOperator() { | ||
| + return new YamlSigilOperator( | ||
| + createSigils( KEY_DEF_DELIM_BEGAN, KEY_DEF_DELIM_ENDED ) | ||
| + ); | ||
| + } | ||
| + | ||
| + public SigilOperator createRSigilOperator() { | ||
| + return new RSigilOperator( | ||
| + createSigils( KEY_R_DELIM_BEGAN, KEY_R_DELIM_ENDED ), | ||
| + createYamlSigilOperator() | ||
| + ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Delegates to {@link #listen(Key, ReadOnlyProperty, BooleanSupplier)}, | ||
| + * providing a value of {@code true} for the {@link BooleanSupplier} to | ||
| + * indicate the property changes always take effect. | ||
| + * | ||
| + * @param key The value to bind to the internal key property. | ||
| + * @param property The external property value that sets the internal value. | ||
| + */ | ||
| + public <T> void listen( final Key key, final ReadOnlyProperty<T> property ) { | ||
| + listen( key, property, () -> true ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Binds a read-only property to a value in the preferences. This allows | ||
| + * user interface properties to change and the preferences will be | ||
| + * synchronized automatically. | ||
| + * <p> | ||
| + * This calls {@link Platform#runLater(Runnable)} to ensure that all pending | ||
| + * application window states are finished before assessing whether property | ||
| + * changes should be applied. Without this, exiting the application while the | ||
| + * window is maximized would persist the window's maximum dimensions, | ||
| + * preventing restoration to its prior, non-maximum size. | ||
| + * </p> | ||
| + * | ||
| + * @param key The value to bind to the internal key property. | ||
| + * @param property The external property value that sets the internal value. | ||
| + * @param enabled Indicates whether property changes should be applied. | ||
| + */ | ||
| + public <T> void listen( | ||
| + final Key key, | ||
| + final ReadOnlyProperty<T> property, | ||
| + final BooleanSupplier enabled ) { | ||
| + property.addListener( | ||
| + ( c, o, n ) -> runLater( () -> { | ||
| + if( enabled.getAsBoolean() ) { | ||
| + valuesProperty( key ).setValue( n ); | ||
| + } | ||
| + } ) | ||
| + ); | ||
| + } | ||
| + | ||
| + public void loadValueKeys( final Consumer<Key> consumer ) { | ||
| + VALUES.keySet().forEach( consumer ); | ||
| + } | ||
| + | ||
| + public void loadSetKeys( final Consumer<Key> consumer ) { | ||
| + SETS.keySet().forEach( consumer ); | ||
| + } | ||
| + | ||
| + public void loadListKeys( final Consumer<Key> consumer ) { | ||
| + LISTS.keySet().forEach( consumer ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Calls the given consumer for all single-value keys. Sets use | ||
| + * {@link #saveSets(BiConsumer)} and lists use {@link #saveLists(BiConsumer)}. | ||
| + * | ||
| + * @param consumer Called to accept each preference key value. | ||
| + */ | ||
| + public void saveValues( final BiConsumer<Key, Property<?>> consumer ) { | ||
| + VALUES.forEach( consumer ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Calls the given consumer for all multi-value keys. For single items, see | ||
| + * {@link #saveValues(BiConsumer)}. Callers are responsible for iterating | ||
| + * over the list of items retrieved through this method. | ||
| + * | ||
| + * @param consumer Called to accept each preference key in the set. | ||
| + */ | ||
| + public void saveSets( final BiConsumer<Key, SetProperty<?>> consumer ) { | ||
| + SETS.forEach( consumer ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Calls the given consumer for all multi-value keys. For single items, see | ||
| + * {@link #saveValues(BiConsumer)}. Callers are responsible for iterating | ||
| + * over the list of items retrieved through this method. | ||
| + * | ||
| + * @param consumer Called to accept each preference key in the list. | ||
| + */ | ||
| + public void saveLists( final BiConsumer<Key, ListProperty<?>> consumer ) { | ||
| + LISTS.forEach( consumer ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Saves the current workspace. | ||
| + */ | ||
| + public void save() { | ||
| + try { | ||
| + final var config = new XMLConfiguration(); | ||
| + | ||
| + // The root config key can only be set for an empty configuration file. | ||
| + config.setRootElementName( APP_TITLE_LOWERCASE ); | ||
| + valuesProperty( KEY_META_VERSION ).setValue( getVersion() ); | ||
| + | ||
| + saveValues( | ||
| + ( key, property ) -> | ||
| + config.setProperty( key.toString(), marshall( property ) ) | ||
| + ); | ||
| + | ||
| + saveSets( | ||
| + ( key, set ) -> { | ||
| + final var keyName = key.toString(); | ||
| + set.forEach( value -> config.addProperty( keyName, value ) ); | ||
| + } | ||
| + ); | ||
| + | ||
| + saveLists( | ||
| + ( key, list ) -> { | ||
| + final var keyName = key.toString(); | ||
| + list.forEach( | ||
| + value -> System.out.printf( "SAVE K/V: %s = %s%n", keyName, value ) | ||
| + ); | ||
| + } | ||
| + ); | ||
| + | ||
| + new FileHandler( config ).save( FILE_PREFERENCES ); | ||
| + } catch( final Exception ex ) { | ||
| + clue( ex ); | ||
| + } | ||
| + } | ||
| + | ||
| + /** | ||
| + * Attempts to load the {@link Constants#FILE_PREFERENCES} configuration file. | ||
| + * If not found, this will fall back to an empty configuration file, leaving | ||
| + * the application to fill in default values. | ||
| + * | ||
| + * @param filename The file containing user preferences to load. | ||
| + */ | ||
| + private void load( final String filename ) { | ||
| + try { | ||
| + final var config = new Configurations().xml( filename ); | ||
| + | ||
| + loadValueKeys( key -> { | ||
| + final var configValue = config.getProperty( key.toString() ); | ||
| + | ||
| + // Allow other properties to load, even if any are missing. | ||
| + if( configValue != null ) { | ||
| + final var property = valuesProperty( key ); | ||
| + property.setValue( unmarshall( property, configValue ) ); | ||
| + } | ||
| + } ); | ||
| + | ||
| + loadSetKeys( key -> { | ||
| + final var configSet = | ||
| + new LinkedHashSet<>( config.getList( key.toString() ) ); | ||
| + final var property = setsProperty( key ); | ||
| + property.setValue( observableSet( configSet ) ); | ||
| + } ); | ||
| + | ||
| + loadListKeys( key -> { | ||
| + System.out.println( "LOAD LIST KEY: " + key ); | ||
| + final var configList = | ||
| + new LinkedList<>( config.getList( key.toString() ) ); | ||
| + final var property = listsProperty( key ); | ||
| + property.setValue( observableArrayList( configList ) ); | ||
| } ); | ||
| } catch( final Exception ex ) { |