Dave Jarvis' Repositories

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

Replace Options with WorkspacePreferences

AuthorDaveJarvis <email>
Date2020-12-19 17:19:54 GMT-0800
Commit84fc21f9bcf316f6f45feffa3067910f82d9f45b
Parent1f4ae83
Delta341 lines added, 109 lines removed, 232-line increase
src/main/resources/META-INF/services/com.keenwrite.service.Options
-com.keenwrite.service.impl.DefaultOptions
+
src/main/java/com/keenwrite/ui/actions/FileChooserCommand.java
package com.keenwrite.ui.actions;
-import com.keenwrite.io.FileType;
import com.keenwrite.Messages;
import com.keenwrite.io.File;
-import com.keenwrite.preferences.Workspace;
+import com.keenwrite.io.FileType;
import com.keenwrite.service.Settings;
+import javafx.beans.property.Property;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Window;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static com.keenwrite.Constants.*;
-import static com.keenwrite.io.FileType.*;
import static com.keenwrite.Messages.get;
-import static com.keenwrite.preferences.Workspace.KEY_UI_WORKING_DIR;
+import static com.keenwrite.io.FileType.*;
import static java.lang.String.format;
/**
* Responsible for opening a dialog that provides users with the ability to
* select files.
*/
public class FileChooserCommand {
private static final String FILTER_EXTENSION_TITLES =
- "Dialog.file.choose.filter";
- private static final String PREF_DIRECTORY = DEFAULT_DIRECTORY.toString();
+ "Dialog.file.choose.filter";
- private final Window mWindow;
+ /**
+ * Dialog owner.
+ */
+ private final Window mParent;
- public FileChooserCommand( final Window window ) {
- mWindow = window;
+ /**
+ * Set to the directory of most recently selected file.
+ */
+ private final Property<File> mDirectory;
+
+ /**
+ * Constructs a new {@link FileChooserCommand} that will attach to a given
+ * parent window and update the given property upon a successful selection.
+ *
+ * @param parent The parent window that will own the dialog.
+ * @param directory The most recently opened file's directory property.
+ */
+ public FileChooserCommand(
+ final Window parent, final Property<File> directory ) {
+ mParent = parent;
+ mDirectory = directory;
}
/**
* Returns a list of files to be opened.
*
* @return A non-null, possibly empty list of files to open.
*/
public List<File> openFiles() {
- final var dialog = createFileChooser( "Dialog.file.choose.open.title" );
- final var list = dialog.showOpenMultipleDialog( mWindow );
+ final var dialog = createFileChooser(
+ "Dialog.file.choose.open.title" );
+ final var list = dialog.showOpenMultipleDialog( mParent );
final List<java.io.File> selected = list == null ? List.of() : list;
final var files = new ArrayList<File>( selected.size() );
for( final var file : selected ) {
files.add( new File( file ) );
}
- storeLastDirectory( files );
+ if( !files.isEmpty() ) {
+ setRecentDirectory( files.get( 0 ) );
+ }
return files;
*/
private Optional<File> saveOrExportAs( final FileChooser dialog ) {
- final var selected = dialog.showSaveDialog( mWindow );
+ final var selected = dialog.showSaveDialog( mParent );
final var file = selected == null ? null : new File( selected );
- storeLastDirectory( file );
+ setRecentDirectory( file );
return Optional.ofNullable( file );
chooser.setTitle( get( key ) );
chooser.getExtensionFilters().addAll( createExtensionFilters() );
-
- final var dir = new File(
- getWorkspace().get( KEY_UI_WORKING_DIR, PREF_DIRECTORY )
- );
-
- chooser.setInitialDirectory(
- dir.isDirectory() ? dir : DEFAULT_DIRECTORY.toFile()
- );
+ chooser.setInitialDirectory( mDirectory.getValue() );
return chooser;
* @return A filename filter suitable for use by a FileDialog instance.
*/
- private ExtensionFilter createExtensionFilter( final FileType filetype ) {
+ private ExtensionFilter createExtensionFilter(
+ final FileType filetype ) {
final var tKey = format( "%s.title.%s", FILTER_EXTENSION_TITLES, filetype );
final var eKey = format( "%s.%s", GLOB_PREFIX_FILE, filetype );
return new ExtensionFilter( Messages.get( tKey ), getExtensions( eKey ) );
}
- private void storeLastDirectory( final File file ) {
+ /**
+ * Sets the value for the most recent directly selected. This will get the
+ * parent location from the given file. If the parent is a readable directory
+ * then this will update the most recent directory property.
+ *
+ * @param file A file contained in a directory.
+ */
+ private void setRecentDirectory( final File file ) {
if( file != null ) {
- final var parent = file.getParent();
- getWorkspace().put(
- KEY_UI_WORKING_DIR, parent == null ? PREF_DIRECTORY : parent
- );
- }
- }
+ final var parent = file.getParentFile();
+ final var dir = parent == null ? USER_DIRECTORY : parent;
- private void storeLastDirectory( final List<File> files ) {
- if( files != null && !files.isEmpty() ) {
- storeLastDirectory( files.get( 0 ) );
+ if( dir.isDirectory() && dir.canRead() ) {
+ mDirectory.setValue( new File( dir ) );
+ }
}
}
private List<String> getExtensions( final String key ) {
return getSettings().getStringSettingList( key );
}
- private Settings getSettings() {
+ private static Settings getSettings() {
return sSettings;
- }
-
- private Workspace getWorkspace() {
- return Workspace.getInstance();
}
}
src/main/java/com/keenwrite/preferences/UserPreferencesView.java
return PreferencesFx.of(
- UserPreferences.class,
+ new XmlStorageHandler(),
Category.of(
get( "Preferences.r" ),
src/main/java/com/keenwrite/preferences/Workspace.java
import com.keenwrite.Constants;
-import com.keenwrite.editors.TextDefinition;
-import com.keenwrite.editors.TextEditor;
import com.keenwrite.io.File;
import org.apache.commons.configuration2.XMLConfiguration;
import static com.keenwrite.Bootstrap.APP_TITLE_LOWERCASE;
import static com.keenwrite.Constants.FILE_PREFERENCES;
-import static com.keenwrite.Launcher.getVersion;
import static com.keenwrite.StatusBarNotifier.clue;
import static java.lang.String.format;
*/
public final class Workspace {
-
- /**
- * Initialization-on-demand design pattern for a lazily-loaded singleton.
- */
- private static class Container {
- private static final Workspace INSTANCE = new Workspace( "default" );
- }
-
- /**
- * Returns the singleton instance for rendering math symbols.
- *
- * @return A non-null instance, loaded, configured, and ready to render math.
- */
- public static Workspace getInstance() {
- return Workspace.Container.INSTANCE;
- }
-
- /**
- * Key for information about the workspace, such as its name and version.
- */
- public static final String KEY_META = "workspace.meta.";
-
- /**
- * Key for files that were opened when the application was closed.
- */
- public static final String KEY_UI_FILES_PATH = "workspace.ui.files.path";
-
- /**
- * Key for last directory selected when opening, saving, or exporting files.
- */
- public static final String KEY_UI_WORKING_DIR = "workspace.ui.dir";
-
- /**
- * Key for recently edited {@link TextEditor}; select, focus tab upon launch.
- */
- public static final String KEY_UI_EDITOR_TEXT_ACTIVE =
- "workspace.ui.text.editor.active";
/**
- * Key for recently edited {@link TextDefinition}; select tab upon launch.
+ * Defines observable user preferences properties and lists.
*/
- public static final String KEY_UI_EDITOR_DEFINITION_ACTIVE =
- "workspace.ui.text.definition.active";
+ private final WorkspacePreferences mPreferences;
/**
*/
private final XMLConfiguration mConfig;
-
- /**
- * User-defined name for this workspace.
- */
- private final String mProject;
/**
* Constructs a new workspace with the given identifier. This will attempt
* to read the configuration file stored in the
- *
- * @param project The unique identifier for this workspace.
*/
- public Workspace( final String project ) {
+ public Workspace( final WorkspacePreferences preferences ) {
+ mPreferences = preferences;
mConfig = load();
- mProject = project;
}
/**
* Saves the current workspace.
*/
- public void save() {
+ public void save( final WorkspacePreferences preferences ) {
try {
- mConfig.setProperty( KEY_META + "version", getVersion() );
- mConfig.setProperty( KEY_META + "name", mProject );
+ // TODO: Export preferences
new FileHandler( mConfig ).save( FILE_PREFERENCES );
} catch( final Exception ex ) {
private XMLConfiguration load() {
try {
- return new Configurations().xml( FILE_PREFERENCES );
+ final var config = new Configurations().xml( FILE_PREFERENCES );
+ // TODO: Import preferences
+
+ return config;
} catch( final Exception ex ) {
clue( ex );
/**
- * Delegates to {@link #put(String, String)} after converting the given
+ * Delegates to {@link #put(Key, String)} after converting the given
* {@link Path} to a string (using the absolute path).
*
* @param key The document key to change.
* @param path Path to a file or directory to store in the settings.
*/
- public void put( final String key, final Path path ) {
+ public void put( final Key key, final Path path ) {
put( key, toString( path ) );
}
* @param value The new value for the key.
*/
- public void put( final String key, final String value ) {
- mConfig.setProperty( key, value );
+ public void put( final Key key, final String value ) {
+ mConfig.setProperty( key.toString(), value );
}
/**
* Returns the value for the key from the application settings.
*
* @param key The key to look up in the settings.
* @param defaultValue The default value to return if the key is not set.
* @return The value for the given key, or the given default if none found.
*/
- public String get( final String key, final String defaultValue ) {
- final var prop = mConfig.getProperty( key );
+ public String get( final Key key, final String defaultValue ) {
+ final var prop = mConfig.getProperty( key.toString() );
return prop == null ? defaultValue : prop.toString();
}
* @param file Absolute path of filename stored at the given key.
*/
- public void putListItem( final String key, final File file ) {
- mConfig.addProperty( key, toString( file ) );
+ public void putListItem( final Key key, final File file ) {
+ mConfig.addProperty( key.toString(), toString( file ) );
}
/**
* Returns the list of files opened for this {@link Workspace}.
*
* @param key The document hierarchy key name.
* @return A non-null, possibly empty list of {@link File} instances.
*/
- public List<File> getListFiles( final String key ) {
+ public List<File> getListFiles( final Key key ) {
final var items = getListItems( key );
final var files = new HashSet<File>( items.size() );
// returned by this method. Re-opening adds the files back to the config.
// This ensures that the list never grows beyond a reasonable number.
- mConfig.clearProperty( key );
+ mConfig.clearProperty( key.toString() );
return new ArrayList<>( files );
* given key.
*/
- private List<String> getListItems( final String key ) {
- return mConfig.getList( String.class, key, new ArrayList<>() );
+ private List<String> getListItems( final Key key ) {
+ return mConfig.getList( String.class, key.toString(), new ArrayList<>() );
}
* @param file The file to remove from the list files opened for editing.
*/
- public void purgeListItem( final String key, final File file ) {
+ public void purgeListItem( final Key key, final File file ) {
final var items = getListItems( key );
final var index = items.indexOf( toString( file ) );
// The list index is 0-based.
if( index >= 0 ) {
mConfig.clearTree( format( "%s(%d)", key, index ) );
}
+ }
+
+ public WorkspacePreferences getPreferences() {
+ return mPreferences;
}
src/main/java/com/keenwrite/preferences/WorkspacePreferences.java
+/* Copyright 2020 White Magic Software, Ltd. -- All rights reserved. */
+package com.keenwrite.preferences;
+
+import com.keenwrite.io.File;
+import javafx.beans.binding.Bindings;
+import javafx.beans.property.*;
+
+import java.util.Map;
+
+import static com.keenwrite.Constants.*;
+import static com.keenwrite.Launcher.getVersion;
+import static com.keenwrite.preferences.Key.key;
+import static java.util.Map.entry;
+
+public class WorkspacePreferences {
+ private static final Key KEY_ROOT = key( "workspace" );
+
+ public static final Key KEY_META = key( KEY_ROOT, "meta" );
+ public static final Key KEY_META_NAME = key( KEY_META, "name" );
+ public static final Key KEY_META_VERSION = key( KEY_META, "version" );
+
+ public static final Key KEY_R = key( KEY_ROOT, "r" );
+ public static final Key KEY_R_SCRIPT = key( KEY_R, "script" );
+ public static final Key KEY_R_DIR = key( KEY_R, "dir" );
+ public static final Key KEY_R_DELIM = key( KEY_R, "delimiter" );
+ public static final Key KEY_R_DELIM_BEGAN = key( KEY_R_DELIM, "began" );
+ public static final Key KEY_R_DELIM_ENDED = key( KEY_R_DELIM, "ended" );
+
+ public static final Key KEY_IMAGES = key( KEY_ROOT, "images" );
+ public static final Key KEY_IMAGES_DIR = key( KEY_IMAGES, "dir" );
+ public static final Key KEY_IMAGES_ORDER = key( KEY_IMAGES, "order" );
+
+ public static final Key KEY_DEF = key( KEY_ROOT, "definition" );
+ public static final Key KEY_DEF_PATH = key( KEY_DEF, "path" );
+ public static final Key KEY_DEF_DELIM = key( KEY_DEF, "delimiter" );
+ public static final Key KEY_DEF_DELIM_BEGAN = key( KEY_DEF_DELIM, "began" );
+ public static final Key KEY_DEF_DELIM_ENDED = key( KEY_DEF_DELIM, "ended" );
+
+ //@formatter:off
+ public static final Key KEY_UI = key( KEY_ROOT, "ui" );
+
+ public static final Key KEY_UI_RECENT = key( KEY_UI, "recent" );
+ public static final Key KEY_UI_RECENT_DIR = key( KEY_UI_RECENT, "dir" );
+ public static final Key KEY_UI_RECENT_DOCUMENT = key( KEY_UI_RECENT,"document" );
+ public static final Key KEY_UI_RECENT_DEFINITION = key( KEY_UI_RECENT, "definition" );
+
+ public static final Key KEY_UI_FILES = key( KEY_UI, "files" );
+ public static final Key KEY_UI_FILES_PATH = key( KEY_UI_FILES, "path" );
+
+ public static final Key KEY_UI_FONT = key( KEY_UI, "font" );
+ public static final Key KEY_UI_FONT_LOCALE = key( KEY_UI_FONT, "locale" );
+ public static final Key KEY_UI_FONT_EDITOR = key( KEY_UI_FONT, "editor" );
+ public static final Key KEY_UI_FONT_EDITOR_SIZE = key( KEY_UI_FONT_EDITOR, "size" );
+ public static final Key KEY_UI_FONT_PREVIEW = key( KEY_UI_FONT, "preview" );
+ public static final Key KEY_UI_FONT_PREVIEW_SIZE = key( KEY_UI_FONT_PREVIEW, "size" );
+
+ public static final Key KEY_UI_WINDOW = key( KEY_UI, "window" );
+ public static final Key KEY_UI_WINDOW_X = key( KEY_UI_WINDOW, "x" );
+ public static final Key KEY_UI_WINDOW_Y = key( KEY_UI_WINDOW, "y" );
+ public static final Key KEY_UI_WINDOW_W = key( KEY_UI_WINDOW, "width" );
+ public static final Key KEY_UI_WINDOW_H = key( KEY_UI_WINDOW, "height" );
+ public static final Key KEY_UI_WINDOW_MAX = key( KEY_UI_WINDOW, "maximized" );
+ public static final Key KEY_UI_WINDOW_FULL = key( KEY_UI_WINDOW, "full" );
+
+ private final Map<Key, Property<?>> VALUES = Map.ofEntries(
+ entry( KEY_META_VERSION, new SimpleStringProperty( getVersion() ) ),
+ entry( KEY_META_NAME, new SimpleStringProperty( "defaullt" ) ),
+
+ entry( KEY_R_SCRIPT, new SimpleStringProperty( "" ) ),
+ entry( KEY_R_DIR, new SimpleObjectProperty<>( USER_DIRECTORY ) ),
+ entry( KEY_R_DELIM_BEGAN, new SimpleStringProperty( R_DELIM_BEGAN_DEFAULT ) ),
+ entry( KEY_R_DELIM_ENDED, new SimpleStringProperty( R_DELIM_ENDED_DEFAULT ) ),
+
+ entry( KEY_IMAGES_DIR, new SimpleObjectProperty<>( USER_DIRECTORY ) ),
+ entry( KEY_IMAGES_ORDER, new SimpleStringProperty( PERSIST_IMAGES_DEFAULT ) ),
+
+ entry( KEY_DEF_PATH, new SimpleObjectProperty<>( DEFINITION_DEFAULT ) ),
+ entry( KEY_DEF_DELIM_BEGAN, new SimpleStringProperty( DEF_DELIM_BEGAN_DEFAULT ) ),
+ entry( KEY_DEF_DELIM_ENDED, new SimpleStringProperty( DEF_DELIM_ENDED_DEFAULT ) ),
+
+ entry( KEY_UI_RECENT_DIR, new SimpleObjectProperty<>( USER_DIRECTORY ) ),
+ entry( KEY_UI_RECENT_DOCUMENT, new SimpleObjectProperty<>( DOCUMENT_DEFAULT ) ),
+ entry( KEY_UI_RECENT_DEFINITION, new SimpleObjectProperty<>( DEFINITION_DEFAULT ) ),
+
+ entry( KEY_UI_FONT_LOCALE, new SimpleObjectProperty<>( LOCALE_DEFAULT ) ),
+ entry( KEY_UI_FONT_EDITOR_SIZE, new SimpleFloatProperty( FONT_SIZE_EDITOR_DEFAULT ) ),
+ entry( KEY_UI_FONT_PREVIEW_SIZE, new SimpleFloatProperty( FONT_SIZE_PREVIEW_DEFAULT ) ),
+
+ entry( KEY_UI_WINDOW_X, new SimpleDoubleProperty( WINDOW_X_DEFAULT ) ),
+ entry( KEY_UI_WINDOW_Y, new SimpleDoubleProperty( WINDOW_Y_DEFAULT ) ),
+ entry( KEY_UI_WINDOW_W, new SimpleDoubleProperty( WINDOW_W_DEFAULT ) ),
+ entry( KEY_UI_WINDOW_H, new SimpleDoubleProperty( WINDOW_H_DEFAULT ) ),
+ entry( KEY_UI_WINDOW_MAX, new SimpleBooleanProperty() ),
+ entry( KEY_UI_WINDOW_FULL, new SimpleBooleanProperty() )
+ //@formatter:on
+ );
+
+ private final Map<Key, ListProperty<?>> LISTS = Map.ofEntries(
+ entry( KEY_UI_FILES_PATH, new SimpleListProperty<>() )
+ );
+
+ public WorkspacePreferences() {
+ }
+
+ /**
+ * Returns a value that represents a setting in the application that the user
+ * may configure.
+ *
+ * @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> Property<T> get( final Key key ) {
+ // The type that goes into the map must come out.
+ return (Property<T>) VALUES.get( key );
+ }
+
+ public double getDouble( final Key key ) {
+ return (double) get( key ).getValue();
+ }
+
+ public boolean getBoolean( final Key key ) {
+ return (boolean) get( key ).getValue();
+ }
+
+ public Property<File> getFile( final Key key ) {
+ return get( key );
+ }
+
+ /**
+ * Returns a value that represents a list of items in the application that
+ * the user may configure, either directly or indirectly, such as the list
+ * of opened files.
+ *
+ * @param key Unique identifier for the particular list.
+ * @return An observable property to be persisted.
+ */
+ public ListProperty<?> getList( final Key key ) {
+ return LISTS.get( key );
+ }
+
+ /**
+ * 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.
+ *
+ * @param key The value to bind to the internal key property.
+ * @param property The external property value that sets the internal value.
+ */
+ public void bind( final Key key, final ReadOnlyDoubleProperty property ) {
+ get( key ).bind( Bindings.createDoubleBinding( property::getValue ) );
+ }
+
+ /**
+ * 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.
+ *
+ * @param key The value to bind to the internal key property.
+ * @param property The external property value that sets the internal value.
+ */
+ public void bind( final Key key, final ReadOnlyBooleanProperty property ) {
+ get( key ).bind( Bindings.createBooleanBinding( property::getValue ) );
+ }
+}
src/main/java/com/keenwrite/preferences/XmlStorageHandler.java
+/* Copyright 2020 White Magic Software, Ltd. -- All rights reserved. */
+package com.keenwrite.preferences;
+
+import com.dlsc.preferencesfx.util.StorageHandler;
+import javafx.collections.ObservableList;
+
+import java.util.prefs.Preferences;
+
+public class XmlStorageHandler implements StorageHandler {
+ @Override
+ public void saveSelectedCategory( final String breadcrumb ) { }
+
+ @Override
+ public String loadSelectedCategory() {
+ return null;
+ }
+
+ @Override
+ public void saveDividerPosition( final double dividerPosition ) {
+ }
+
+ @Override
+ public double loadDividerPosition() {
+ return 0;
+ }
+
+ @Override
+ public void saveWindowWidth( final double windowWidth ) { }
+
+ @Override
+ public double loadWindowWidth() {
+ return 0;
+ }
+
+ @Override
+ public void saveWindowHeight( final double windowHeight ) { }
+
+ @Override
+ public double loadWindowHeight() {
+ return 0;
+ }
+
+ @Override
+ public void saveWindowPosX( final double windowPosX ) { }
+
+ @Override
+ public double loadWindowPosX() {
+ return 0;
+ }
+
+ @Override
+ public void saveWindowPosY( final double windowPosY ) { }
+
+ @Override
+ public double loadWindowPosY() {
+ return 0;
+ }
+
+ @Override
+ public void saveObject( final String breadcrumb, final Object object ) { }
+
+ @Override
+ public Object loadObject(
+ final String breadcrumb, final Object defaultObject ) {
+ return null;
+ }
+
+ @Override
+ public <T> T loadObject(
+ final String breadcrumb, final Class<T> type, final T defaultObject ) {
+ return null;
+ }
+
+ @Override
+ public ObservableList loadObservableList(
+ final String breadcrumb, final ObservableList defaultObservableList ) {
+ return null;
+ }
+
+ @Override
+ public <T> ObservableList<T> loadObservableList(
+ final String breadcrumb,
+ final Class<T> type,
+ final ObservableList<T> defaultObservableList ) {
+ return null;
+ }
+
+ @Override
+ public boolean clearPreferences() {
+ return false;
+ }
+
+ @Override
+ public Preferences getPreferences() {
+ return null;
+ }
+}