From a8b85f6483025fe233f3605b3b078427338d4855 Mon Sep 17 00:00:00 2001 From: Mark Raynsford Date: Sun, 22 Sep 2024 20:54:06 +0000 Subject: [PATCH] Initial global captions. --- .../io7m/laurel/filemodel/LFileModelType.java | 31 ++- .../internal/LCommandGlobalCaptionsAdd.java | 225 ++++++++++++++++++ .../LCommandGlobalCaptionsRemove.java | 224 +++++++++++++++++ .../filemodel/internal/LCommandLoad.java | 1 + .../internal/LCommandModelUpdates.java | 22 ++ .../laurel/filemodel/internal/LFileModel.java | 40 ++++ .../src/main/java/module-info.java | 4 + .../laurel/filemodel/internal/database.xml | 20 ++ .../gui/internal/LAbstractViewWithModel.java | 21 +- .../laurel/gui/internal/LCaptionsView.java | 20 +- .../laurel/gui/internal/LCategoriesView.java | 45 +++- .../gui/internal/LGlobalPrefixCaptions.java | 70 ++++-- .../laurel/gui/internal/Messages.properties | 6 +- .../io7m/laurel/gui/internal/captionEdit.fxml | 46 ++-- .../io7m/laurel/gui/internal/categories.fxml | 31 ++- .../gui/internal/category-require-off.png | Bin 0 -> 274 bytes .../laurel/gui/internal/category-require.png | Bin 0 -> 464 bytes .../gui/internal/globalPrefixCaptions.fxml | 12 +- com.io7m.laurel.tests/pom.xml | 15 -- .../com/io7m/laurel/tests/LFileModelTest.java | 59 +++++ 20 files changed, 806 insertions(+), 86 deletions(-) create mode 100644 com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandGlobalCaptionsAdd.java create mode 100644 com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandGlobalCaptionsRemove.java create mode 100644 com.io7m.laurel.gui/src/main/resources/com/io7m/laurel/gui/internal/category-require-off.png create mode 100644 com.io7m.laurel.gui/src/main/resources/com/io7m/laurel/gui/internal/category-require.png diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelType.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelType.java index a565cf0..630a81e 100644 --- a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelType.java +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelType.java @@ -18,8 +18,9 @@ package com.io7m.laurel.filemodel; import com.io7m.jattribute.core.AttributeReadableType; -import com.io7m.laurel.model.LCaptionName; import com.io7m.laurel.model.LCaption; +import com.io7m.laurel.model.LCaptionID; +import com.io7m.laurel.model.LCaptionName; import com.io7m.laurel.model.LCategory; import com.io7m.laurel.model.LCategoryID; import com.io7m.laurel.model.LCategoryName; @@ -51,6 +52,28 @@ public interface LFileModelType Flow.Publisher events(); + /** + * Add a global caption. + * + * @param text The caption + * + * @return The operation in progress + */ + + CompletableFuture globalCaptionAdd( + LCaptionName text); + + /** + * Remove a global caption. + * + * @param id The caption + * + * @return The operation in progress + */ + + CompletableFuture globalCaptionRemove( + LCaptionID id); + /** * Set categories as required. * @@ -331,4 +354,10 @@ void close() */ CompletableFuture> imageStream(LImageID id); + + /** + * @return The current complete list of global captions + */ + + AttributeReadableType> globalCaptionList(); } diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandGlobalCaptionsAdd.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandGlobalCaptionsAdd.java new file mode 100644 index 0000000..c51874a --- /dev/null +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandGlobalCaptionsAdd.java @@ -0,0 +1,225 @@ +/* + * Copyright © 2024 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.laurel.filemodel.internal; + +import com.io7m.laurel.model.LCaptionName; +import org.jooq.DSLContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static com.io7m.laurel.filemodel.internal.Tables.GLOBAL_CAPTIONS; + +/** + * Add global captions. + */ + +public final class LCommandGlobalCaptionsAdd + extends LCommandAbstract> +{ + private final ArrayList savedData; + + private record SavedData( + long id, + String text) + { + + } + + /** + * Add global captions. + */ + + public LCommandGlobalCaptionsAdd() + { + this.savedData = new ArrayList<>(); + } + + /** + * Add global captions. + * + * @return A command factory + */ + + public static LCommandFactoryType> provider() + { + return new LCommandFactory<>( + LCommandGlobalCaptionsAdd.class.getCanonicalName(), + LCommandGlobalCaptionsAdd::fromProperties + ); + } + + private static LCommandGlobalCaptionsAdd fromProperties( + final Properties p) + { + final var c = new LCommandGlobalCaptionsAdd(); + + for (int index = 0; index < Integer.MAX_VALUE; ++index) { + final var idKey = + "caption.%d.id".formatted(Integer.valueOf(index)); + final var textKey = + "caption.%d.text".formatted(Integer.valueOf(index)); + + if (!p.containsKey(idKey)) { + break; + } + + final var data = + new SavedData( + Long.parseUnsignedLong(p.getProperty(idKey)), + p.getProperty(textKey) + ); + + c.savedData.add(data); + } + + c.setExecuted(true); + return c; + } + + @Override + protected LCommandUndoable onExecute( + final LFileModel model, + final LDatabaseTransactionType transaction, + final List captions) + { + final var context = + transaction.get(DSLContext.class); + + final var max = captions.size(); + for (int index = 0; index < max; ++index) { + final var caption = captions.get(index); + model.eventWithProgressCurrentMax(index, max, "Adding caption '%s'", caption); + + final var recOpt = + context.insertInto(GLOBAL_CAPTIONS) + .set(GLOBAL_CAPTIONS.GLOBAL_CAPTION_TEXT, caption.text()) + .onDuplicateKeyIgnore() + .returning(GLOBAL_CAPTIONS.GLOBAL_CAPTION_ID) + .fetchOptional(); + + if (recOpt.isEmpty()) { + model.eventWithProgressCurrentMax( + index, + max, + "Caption '%s' already existed.", + caption + ); + continue; + } + + final var rec = recOpt.get(); + this.savedData.add( + new SavedData( + rec.get(GLOBAL_CAPTIONS.GLOBAL_CAPTION_ID).longValue(), + caption.text() + ) + ); + } + + model.eventWithoutProgress("Added %d captions.", this.savedData.size()); + model.setGlobalCaptions(LCommandModelUpdates.listGlobalCaptions(context)); + + if (!this.savedData.isEmpty()) { + return LCommandUndoable.COMMAND_UNDOABLE; + } + + return LCommandUndoable.COMMAND_NOT_UNDOABLE; + } + + @Override + protected void onUndo( + final LFileModel model, + final LDatabaseTransactionType transaction) + { + final var context = + transaction.get(DSLContext.class); + + final var max = this.savedData.size(); + for (int index = 0; index < max; ++index) { + final var data = this.savedData.get(index); + model.eventWithProgressCurrentMax( + index, + max, + "Removing caption '%s'", + data.text + ); + context.deleteFrom(GLOBAL_CAPTIONS) + .where(GLOBAL_CAPTIONS.GLOBAL_CAPTION_ID.eq(data.id)) + .execute(); + } + + model.eventWithoutProgress("Removed %d captions.", Integer.valueOf(max)); + model.setGlobalCaptions(LCommandModelUpdates.listGlobalCaptions(context)); + } + + @Override + protected void onRedo( + final LFileModel model, + final LDatabaseTransactionType transaction) + { + final var context = + transaction.get(DSLContext.class); + + final var max = this.savedData.size(); + for (int index = 0; index < max; ++index) { + final var data = this.savedData.get(index); + model.eventWithProgressCurrentMax( + index, + max, + "Adding caption '%s'", + data.text + ); + context.insertInto(GLOBAL_CAPTIONS) + .set(GLOBAL_CAPTIONS.GLOBAL_CAPTION_ID, data.id) + .set(GLOBAL_CAPTIONS.GLOBAL_CAPTION_TEXT, data.text) + .onDuplicateKeyUpdate() + .set(GLOBAL_CAPTIONS.GLOBAL_CAPTION_TEXT, data.text) + .execute(); + } + + model.eventWithoutProgress("Added %d captions.", Integer.valueOf(max)); + model.setGlobalCaptions(LCommandModelUpdates.listGlobalCaptions(context)); + } + + @Override + public Properties toProperties() + { + final var p = new Properties(); + + for (int index = 0; index < this.savedData.size(); ++index) { + final var idKey = + "caption.%d.id".formatted(Integer.valueOf(index)); + final var textKey = + "caption.%d.text".formatted(Integer.valueOf(index)); + + final var data = this.savedData.get(index); + p.setProperty(idKey, Long.toUnsignedString(data.id)); + p.setProperty(textKey, data.text); + } + + return p; + } + + @Override + public String describe() + { + return "Add global caption(s)"; + } +} diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandGlobalCaptionsRemove.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandGlobalCaptionsRemove.java new file mode 100644 index 0000000..45f522a --- /dev/null +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandGlobalCaptionsRemove.java @@ -0,0 +1,224 @@ +/* + * Copyright © 2024 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.laurel.filemodel.internal; + +import com.io7m.laurel.model.LCaptionID; +import org.jooq.DSLContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static com.io7m.laurel.filemodel.internal.Tables.GLOBAL_CAPTIONS; + +/** + * Remove global captions. + */ + +public final class LCommandGlobalCaptionsRemove + extends LCommandAbstract> +{ + private final ArrayList savedData; + + private record SavedData( + long id, + String text) + { + + } + + /** + * Remove global captions. + */ + + public LCommandGlobalCaptionsRemove() + { + this.savedData = new ArrayList<>(); + } + + /** + * Remove global captions. + * + * @return A command factory + */ + + public static LCommandFactoryType> provider() + { + return new LCommandFactory<>( + LCommandGlobalCaptionsRemove.class.getCanonicalName(), + LCommandGlobalCaptionsRemove::fromProperties + ); + } + + private static LCommandGlobalCaptionsRemove fromProperties( + final Properties p) + { + final var c = new LCommandGlobalCaptionsRemove(); + + for (int index = 0; index < Integer.MAX_VALUE; ++index) { + final var idKey = + "caption.%d.id".formatted(Integer.valueOf(index)); + final var textKey = + "caption.%d.text".formatted(Integer.valueOf(index)); + + if (!p.containsKey(idKey)) { + break; + } + + final var data = + new SavedData( + Long.parseUnsignedLong(p.getProperty(idKey)), + p.getProperty(textKey) + ); + + c.savedData.add(data); + } + + c.setExecuted(true); + return c; + } + + @Override + protected LCommandUndoable onExecute( + final LFileModel model, + final LDatabaseTransactionType transaction, + final List captions) + { + final var context = + transaction.get(DSLContext.class); + + final var max = captions.size(); + for (int index = 0; index < max; ++index) { + final var caption = captions.get(index); + model.eventWithProgressCurrentMax( + index, + max, + "Removing caption '%s'", + caption); + + final var recOpt = + context.deleteFrom(GLOBAL_CAPTIONS) + .where(GLOBAL_CAPTIONS.GLOBAL_CAPTION_ID.eq(caption.value())) + .returning(GLOBAL_CAPTIONS.GLOBAL_CAPTION_TEXT) + .fetchOptional(GLOBAL_CAPTIONS.GLOBAL_CAPTION_TEXT); + + if (recOpt.isEmpty()) { + model.eventWithProgressCurrentMax( + index, + max, + "Caption '%s' did not exist.", + caption + ); + continue; + } + + final var rec = recOpt.get(); + this.savedData.add(new SavedData(caption.value(), rec)); + } + + model.eventWithoutProgress("Removed %d captions.", this.savedData.size()); + model.setGlobalCaptions(LCommandModelUpdates.listGlobalCaptions(context)); + + if (!this.savedData.isEmpty()) { + return LCommandUndoable.COMMAND_UNDOABLE; + } + + return LCommandUndoable.COMMAND_NOT_UNDOABLE; + } + + @Override + protected void onUndo( + final LFileModel model, + final LDatabaseTransactionType transaction) + { + final var context = + transaction.get(DSLContext.class); + + final var max = this.savedData.size(); + for (int index = 0; index < max; ++index) { + final var data = this.savedData.get(index); + model.eventWithProgressCurrentMax( + index, + max, + "Re-adding caption '%s'", + data.text + ); + + context.insertInto(GLOBAL_CAPTIONS) + .set(GLOBAL_CAPTIONS.GLOBAL_CAPTION_ID, data.id) + .set(GLOBAL_CAPTIONS.GLOBAL_CAPTION_TEXT, data.text) + .onConflictDoNothing() + .execute(); + } + + model.eventWithoutProgress("Re-added %d captions.", Integer.valueOf(max)); + model.setGlobalCaptions(LCommandModelUpdates.listGlobalCaptions(context)); + } + + @Override + protected void onRedo( + final LFileModel model, + final LDatabaseTransactionType transaction) + { + final var context = + transaction.get(DSLContext.class); + + final var max = this.savedData.size(); + for (int index = 0; index < max; ++index) { + final var data = this.savedData.get(index); + model.eventWithProgressCurrentMax( + index, + max, + "Deleting caption '%s'", + data.text + ); + + context.deleteFrom(GLOBAL_CAPTIONS) + .where(GLOBAL_CAPTIONS.GLOBAL_CAPTION_ID.eq(data.id)) + .execute(); + } + + model.eventWithoutProgress("Deleted %d captions.", Integer.valueOf(max)); + model.setGlobalCaptions(LCommandModelUpdates.listGlobalCaptions(context)); + } + + @Override + public Properties toProperties() + { + final var p = new Properties(); + + for (int index = 0; index < this.savedData.size(); ++index) { + final var idKey = + "caption.%d.id".formatted(Integer.valueOf(index)); + final var textKey = + "caption.%d.text".formatted(Integer.valueOf(index)); + + final var data = this.savedData.get(index); + p.setProperty(idKey, Long.toUnsignedString(data.id)); + p.setProperty(textKey, data.text); + } + + return p; + } + + @Override + public String describe() + { + return "Remove global caption(s)"; + } +} diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandLoad.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandLoad.java index 9f2377d..769111d 100644 --- a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandLoad.java +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandLoad.java @@ -73,6 +73,7 @@ protected LCommandUndoable onExecute( LCommandModelUpdates.listCategoriesCaptions(context) ); model.setMetadata(LCommandModelUpdates.listMetadata(context)); + model.setGlobalCaptions(LCommandModelUpdates.listGlobalCaptions(context)); return LCommandUndoable.COMMAND_NOT_UNDOABLE; } diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandModelUpdates.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandModelUpdates.java index 8ab9053..efb13d6 100644 --- a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandModelUpdates.java +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandModelUpdates.java @@ -44,6 +44,7 @@ import static com.io7m.laurel.filemodel.internal.Tables.CAPTIONS; import static com.io7m.laurel.filemodel.internal.Tables.CAPTION_CATEGORIES; import static com.io7m.laurel.filemodel.internal.Tables.CATEGORIES; +import static com.io7m.laurel.filemodel.internal.Tables.GLOBAL_CAPTIONS; import static com.io7m.laurel.filemodel.internal.Tables.IMAGES; import static com.io7m.laurel.filemodel.internal.Tables.IMAGE_BLOBS; import static com.io7m.laurel.filemodel.internal.Tables.IMAGE_CAPTIONS; @@ -324,4 +325,25 @@ static List listMetadata( ); }).toList(); } + + static List listGlobalCaptions( + final DSLContext context) + { + return context.select( + GLOBAL_CAPTIONS.GLOBAL_CAPTION_ID, + GLOBAL_CAPTIONS.GLOBAL_CAPTION_TEXT) + .from(GLOBAL_CAPTIONS) + .orderBy( + GLOBAL_CAPTIONS.GLOBAL_CAPTION_TEXT.asc(), + GLOBAL_CAPTIONS.GLOBAL_CAPTION_ID.asc()) + .stream() + .map(r -> { + return new LCaption( + new LCaptionID(r.get(GLOBAL_CAPTIONS.GLOBAL_CAPTION_ID)), + new LCaptionName(r.get(GLOBAL_CAPTIONS.GLOBAL_CAPTION_TEXT)), + 1L + ); + }) + .toList(); + } } diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LFileModel.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LFileModel.java index f0dfb11..12b24e7 100644 --- a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LFileModel.java +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LFileModel.java @@ -32,6 +32,7 @@ import com.io7m.laurel.filemodel.LFileModelType; import com.io7m.laurel.filemodel.LImageCaptionsAssignment; import com.io7m.laurel.model.LCaption; +import com.io7m.laurel.model.LCaptionID; import com.io7m.laurel.model.LCaptionName; import com.io7m.laurel.model.LCategory; import com.io7m.laurel.model.LCategoryID; @@ -88,6 +89,7 @@ public final class LFileModel implements LFileModelType LOG.error("Uncaught attribute exception: ", throwable); }); + private final AttributeType> globalCaptions; private final AttributeType> categoryCaptionsAssigned; private final AttributeType> categoryCaptionsUnassigned; private final AttributeType> imageCaptionsAssigned; @@ -123,6 +125,8 @@ private LFileModel( ATTRIBUTES.withValue(List.of()); this.categoryCaptions = ATTRIBUTES.withValue(Collections.emptySortedMap()); + this.globalCaptions = + ATTRIBUTES.withValue(List.of()); this.tagsAll = ATTRIBUTES.withValue(List.of()); this.imageCaptionsAssigned = @@ -426,6 +430,30 @@ public SubmissionPublisher events() return this.events; } + @Override + public CompletableFuture globalCaptionAdd( + final LCaptionName text) + { + Objects.requireNonNull(text, "text"); + + return this.runCommand( + new LCommandGlobalCaptionsAdd(), + List.of(text) + ); + } + + @Override + public CompletableFuture globalCaptionRemove( + final LCaptionID id) + { + Objects.requireNonNull(id, "id"); + + return this.runCommand( + new LCommandGlobalCaptionsRemove(), + List.of(id) + ); + } + @Override public CompletableFuture categoryAdd( final LCategoryName text) @@ -859,6 +887,12 @@ public CompletableFuture> imageStream( return future; } + @Override + public AttributeReadableType> globalCaptionList() + { + return this.globalCaptions; + } + private Optional executeImageStream( final LImageID id) throws LException @@ -1014,4 +1048,10 @@ void setMetadata( { this.metadata.set(meta); } + + void setGlobalCaptions( + final List captions) + { + this.globalCaptions.set(captions); + } } diff --git a/com.io7m.laurel.filemodel/src/main/java/module-info.java b/com.io7m.laurel.filemodel/src/main/java/module-info.java index b771bc1..4996d0b 100644 --- a/com.io7m.laurel.filemodel/src/main/java/module-info.java +++ b/com.io7m.laurel.filemodel/src/main/java/module-info.java @@ -20,6 +20,8 @@ import com.io7m.laurel.filemodel.internal.LCommandCategoryCaptionsAssign; import com.io7m.laurel.filemodel.internal.LCommandCategoryCaptionsUnassign; import com.io7m.laurel.filemodel.internal.LCommandFactoryType; +import com.io7m.laurel.filemodel.internal.LCommandGlobalCaptionsAdd; +import com.io7m.laurel.filemodel.internal.LCommandGlobalCaptionsRemove; import com.io7m.laurel.filemodel.internal.LCommandImageSelect; import com.io7m.laurel.filemodel.internal.LCommandImageCaptionsAssign; import com.io7m.laurel.filemodel.internal.LCommandImageCaptionsUnassign; @@ -61,6 +63,8 @@ LCommandCategoriesUnsetRequired, LCommandCategoryCaptionsAssign, LCommandCategoryCaptionsUnassign, + LCommandGlobalCaptionsAdd, + LCommandGlobalCaptionsRemove, LCommandImageCaptionsAssign, LCommandImageCaptionsUnassign, LCommandImageSelect, diff --git a/com.io7m.laurel.filemodel/src/main/resources/com/io7m/laurel/filemodel/internal/database.xml b/com.io7m.laurel.filemodel/src/main/resources/com/io7m/laurel/filemodel/internal/database.xml index 2e75dfe..2957965 100644 --- a/com.io7m.laurel.filemodel/src/main/resources/com/io7m/laurel/filemodel/internal/database.xml +++ b/com.io7m.laurel.filemodel/src/main/resources/com/io7m/laurel/filemodel/internal/database.xml @@ -107,6 +107,26 @@ CREATE TABLE captions ( -- [jooq ignore start] STRICT -- [jooq ignore stop] +]]> + + + The global_captions table stores the complete set of available global captions. + + + diff --git a/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LAbstractViewWithModel.java b/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LAbstractViewWithModel.java index 035be43..b8d3ced 100644 --- a/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LAbstractViewWithModel.java +++ b/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LAbstractViewWithModel.java @@ -55,16 +55,6 @@ public final void initialize( this.onInitialize(); this.fileModel.subscribe((oldValue, newValue) -> { - if (oldValue.isEmpty() && newValue.isPresent()) { - this.fileModelSubscriptions = - CloseableCollection.create(); - this.onFileBecameAvailable( - this.fileModelSubscriptions, - newValue.get() - ); - return; - } - if (oldValue.isPresent() && newValue.isEmpty()) { this.onFileBecameUnavailable(); try { @@ -74,6 +64,17 @@ public final void initialize( } return; } + + if (oldValue.isEmpty() && newValue.isPresent()) { + this.fileModelSubscriptions = CloseableCollection.create(); + } + + if (newValue.isPresent()) { + this.onFileBecameAvailable( + this.fileModelSubscriptions, + newValue.get() + ); + } }); } diff --git a/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LCaptionsView.java b/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LCaptionsView.java index cd2491a..75cfebb 100644 --- a/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LCaptionsView.java +++ b/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LCaptionsView.java @@ -53,6 +53,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -578,15 +579,22 @@ private void onCaptionGlobal() final var loader = new FXMLLoader(layout, this.strings.resources()); - final var globals = - new LGlobalPrefixCaptions( - this.services, - this.fileModelScope(), - stage + final LViewControllerFactoryType controllers = + LViewControllerFactoryMapped.create( + Map.entry( + LGlobalPrefixCaptions.class, + () -> { + return new LGlobalPrefixCaptions( + this.services, + this.fileModelScope(), + stage + ); + } + ) ); loader.setControllerFactory(param -> { - return globals; + return controllers.call((Class) param); }); final Pane pane = loader.load(); diff --git a/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LCategoriesView.java b/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LCategoriesView.java index 680e2f2..f412168 100644 --- a/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LCategoriesView.java +++ b/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LCategoriesView.java @@ -24,6 +24,7 @@ import com.io7m.laurel.model.LCategory; import com.io7m.laurel.model.LCategoryName; import com.io7m.repetoir.core.RPServiceDirectoryType; +import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyStringWrapper; import javafx.collections.FXCollections; import javafx.fxml.FXML; @@ -35,6 +36,7 @@ import java.util.List; import java.util.Optional; +import java.util.Set; /** * The categories view. @@ -51,6 +53,8 @@ public final class LCategoriesView extends LAbstractViewWithModel @FXML private Button categoryRemove; @FXML private Button captionAssign; @FXML private Button captionUnassign; + @FXML private Button categoryRequire; + @FXML private Button categoryUnrequire; LCategoriesView( final RPServiceDirectoryType services, @@ -82,6 +86,28 @@ private void onCategoryRemoveSelected() } + @FXML + private void onCategoryRequireSelected() + { + final var category = + this.categoryList.getSelectionModel() + .getSelectedItem(); + + this.fileModelNow() + .categorySetRequired(Set.of(category.id())); + } + + @FXML + private void onCategoryUnrequireSelected() + { + final var category = + this.categoryList.getSelectionModel() + .getSelectedItem(); + + this.fileModelNow() + .categorySetNotRequired(Set.of(category.id())); + } + @FXML private void onCategoryAssignSelected() { @@ -195,13 +221,20 @@ private void initializeCategoriesTable() final var columns = this.categoryList.getColumns(); - final var colText = (TableColumn) columns.get(0); - colText.setSortable(true); - colText.setReorderable(false); - colText.setCellValueFactory(param -> { + final var colName = (TableColumn) columns.get(0); + colName.setSortable(true); + colName.setReorderable(false); + colName.setCellValueFactory(param -> { return new ReadOnlyStringWrapper(param.getValue().name().text()); }); + final var colReq = (TableColumn) columns.get(1); + colReq.setSortable(true); + colReq.setReorderable(false); + colReq.setCellValueFactory(param -> { + return new ReadOnlyObjectWrapper<>(param.getValue().required()); + }); + this.categoryList.getSelectionModel() .setSelectionMode(SelectionMode.SINGLE); this.categoryList.getSelectionModel() @@ -209,7 +242,7 @@ private void initializeCategoriesTable() .subscribe(this::onCategorySelectionChanged); this.categoryList.setPlaceholder(new Label("")); - this.categoryList.getSortOrder().add(colText); + this.categoryList.getSortOrder().add(colName); this.categoryList.sort(); } @@ -223,6 +256,8 @@ private void onCategorySelectionChanged() this.fileModelNow().categorySelect(selected); this.categoryRemove.setDisable(selected.isEmpty()); + this.categoryRequire.setDisable(selected.isEmpty()); + this.categoryUnrequire.setDisable(selected.isEmpty()); } private void onCaptionsUnassignedSelectionChanged() diff --git a/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LGlobalPrefixCaptions.java b/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LGlobalPrefixCaptions.java index 2cbf0f4..98c3602 100644 --- a/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LGlobalPrefixCaptions.java +++ b/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LGlobalPrefixCaptions.java @@ -19,10 +19,16 @@ import com.io7m.jmulticlose.core.CloseableCollectionType; import com.io7m.laurel.filemodel.LFileModelType; +import com.io7m.laurel.model.LCaption; +import com.io7m.laurel.model.LCaptionName; import com.io7m.repetoir.core.RPServiceDirectoryType; +import javafx.application.Platform; +import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.scene.control.Button; +import javafx.scene.control.ListCell; import javafx.scene.control.ListView; +import javafx.scene.control.SelectionMode; import javafx.stage.Stage; import java.util.Objects; @@ -42,7 +48,7 @@ public final class LGlobalPrefixCaptions @FXML private Button delete; @FXML private Button up; @FXML private Button down; - @FXML private ListView captions; + @FXML private ListView captions; /** * The global prefix captions editor. @@ -73,6 +79,10 @@ protected void onInitialize() this.modify.setDisable(true); this.up.setDisable(true); + this.captions.setCellFactory(v -> new LCaptionListCell()); + this.captions.getSelectionModel() + .setSelectionMode(SelectionMode.SINGLE); + this.captions.getSelectionModel() .selectedItemProperty() .addListener((o, oldCap, newCap) -> { @@ -91,11 +101,18 @@ protected void onFileBecameAvailable( final CloseableCollectionType subscriptions, final LFileModelType fileModel) { - + subscriptions.add( + fileModel.globalCaptionList() + .subscribe((oldValue, newValue) -> { + Platform.runLater(() -> { + this.captions.setItems(FXCollections.observableList(newValue)); + }); + }) + ); } private void onCaptionSelectionChanged( - final String caption) + final LCaption caption) { if (caption != null) { this.delete.setDisable(false); @@ -121,37 +138,24 @@ private void onCreateCaptionSelected() final var result = editor.result(); if (result.isPresent()) { final var text = result.get(); - // this.controller.globalPrefixCaptionNew(text); + this.fileModelNow().globalCaptionAdd(new LCaptionName(text)); } } @FXML private void onDeleteCaptionSelected() { - // this.controller.globalPrefixCaptionDelete( - // this.captions.getSelectionModel() - // .getSelectedIndex() - // ); + this.fileModelNow().globalCaptionRemove( + this.captions.getSelectionModel() + .getSelectedItem() + .id() + ); } @FXML private void onModifyCaptionSelected() { - final var editor = - this.editors.open( - this.captions.getSelectionModel() - .getSelectedItem() - ); - final var result = editor.result(); - if (result.isPresent()) { - final var text = result.get(); - // this.controller.globalPrefixCaptionModify( - // this.captions.getSelectionModel() - // .getSelectedIndex(), - // text - // ); - } } @FXML @@ -165,4 +169,26 @@ private void onCaptionDownSelected() { } + + private static final class LCaptionListCell + extends ListCell + { + LCaptionListCell() + { + + } + + @Override + protected void updateItem( + final LCaption caption, + final boolean isEmpty) + { + super.updateItem(caption, isEmpty); + this.setGraphic(null); + this.setText(null); + if (caption != null) { + this.setText(caption.name().text()); + } + } + } } diff --git a/com.io7m.laurel.gui/src/main/resources/com/io7m/laurel/gui/internal/Messages.properties b/com.io7m.laurel.gui/src/main/resources/com/io7m/laurel/gui/internal/Messages.properties index f95ad5a..483a276 100644 --- a/com.io7m.laurel.gui/src/main/resources/com/io7m/laurel/gui/internal/Messages.properties +++ b/com.io7m.laurel.gui/src/main/resources/com/io7m/laurel/gui/internal/Messages.properties @@ -35,6 +35,7 @@ captions.compare=Compare captions captions.tooltip.add=Add new caption. captions.tooltip.add_to_image=Add the selected caption to the current image. captions.tooltip.delete=Delete the selected caption. +captions.tooltip.modify=Modify the selected caption. captions.tooltip.global=Configure global prefix captions. captions.tooltip.priority_down=Reduce caption priority. captions.tooltip.priority_up=Increase caption priority. @@ -44,6 +45,8 @@ categories.tooltip.add=Add a new category. categories.tooltip.assign=Assign captions to the category. categories.tooltip.delete=Delete the selected category. categories.tooltip.unassign=Unassign captions from the category. +categories.tooltip.require=Mark categories as required. +categories.tooltip.require_off=Mark categories as not required. categories=Categories category.edit=Specify the category text. category=Category @@ -68,15 +71,16 @@ images.tooltip.delete=Delete the selected image. images.tooltip.search=Search for images with the given comma-separated list of captions. images=Images import.select=Select a directory filled with captioned images... +metadata.edit=Edit metadata. metadata.tooltip.add=Add a metadata values. metadata.tooltip.delete=Delete metadata values. metadata=Metadata metadata_name=Name metadata_value=Value -metadata.edit=Edit metadata. placeholder=Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Euismod nisi porta lorem mollis aliquam. In cursus turpis massa tincidunt. Felis eget velit aliquet sagittis id consectetur purus ut. Felis eget velit aliquet sagittis id consectetur purus. Lacus sed turpis tincidunt id aliquet risus feugiat in ante. Orci nulla pellentesque dignissim enim. Urna porttitor rhoncus dolor purus non enim praesent elementum facilisis. Tortor aliquam nulla facilisi cras. Feugiat pretium nibh ipsum consequat. Mattis molestie a iaculis at erat pellentesque adipiscing commodo. Sagittis vitae et leo duis. Vitae et leo duis ut diam quam nulla. Ut ornare lectus sit amet est placerat in. Nec sagittis aliquam malesuada bibendum arcu vitae elementum. Duis convallis convallis tellus id interdum velit laoreet id donec. Rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. redo=Redo redo_specific=Redo ({0}) +required=Required save=Save select.new_file=Select a new file... select.save_as=Select a file to save into... diff --git a/com.io7m.laurel.gui/src/main/resources/com/io7m/laurel/gui/internal/captionEdit.fxml b/com.io7m.laurel.gui/src/main/resources/com/io7m/laurel/gui/internal/captionEdit.fxml index 70ae214..9d56198 100644 --- a/com.io7m.laurel.gui/src/main/resources/com/io7m/laurel/gui/internal/captionEdit.fxml +++ b/com.io7m.laurel.gui/src/main/resources/com/io7m/laurel/gui/internal/captionEdit.fxml @@ -6,20 +6,29 @@ + + - - + - - - - + + + + + + + + + + + + + - - - - - + + +