From d9878e2b5d75e5e66ebe3f8e62aaff027f608c6e Mon Sep 17 00:00:00 2001 From: jmillman Date: Wed, 6 Jan 2016 14:50:57 -0500 Subject: [PATCH] use new UndoRedoManager and Command interface; add Redo and improve UX (icons) --- .../imagegallery/ImageGalleryController.java | 6 +- .../actions/CategorizationChangeSet.java | 25 ++- .../actions/CategorizeAction.java | 18 ++- .../autopsy/imagegallery/actions/Command.java | 39 +++++ .../autopsy/imagegallery/actions/Redo.java | 42 +++++ .../autopsy/imagegallery/actions/Undo.java | 24 ++- .../imagegallery/actions/UndoRedoManager.java | 153 ++++++++++++++++++ .../gui/drawableviews/GroupPane.fxml | 4 +- .../gui/drawableviews/GroupPane.java | 6 + .../autopsy/imagegallery/images/redo.png | Bin 0 -> 1892 bytes .../autopsy/imagegallery/images/undo.png | Bin 0 -> 1905 bytes 11 files changed, 292 insertions(+), 25 deletions(-) create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Command.java create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Redo.java create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/UndoRedoManager.java create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/redo.png create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/undo.png diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 202845069a..bf04a9610a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -63,7 +63,7 @@ import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.History; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; -import org.sleuthkit.autopsy.imagegallery.actions.CategorizationChangeSet; +import org.sleuthkit.autopsy.imagegallery.actions.UndoRedoManager; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; @@ -110,7 +110,7 @@ public final class ImageGalleryController { } private final History historyManager = new History<>(); - private final History categoryUndoHistory = new History<>(); + private final UndoRedoManager categoryUndoHistory = new UndoRedoManager(this); /** * true if Image Gallery should listen to ingest events, false if it should @@ -475,7 +475,7 @@ public final class ImageGalleryController { this.navPanel = navPanel; } - public History getUndoHistory() { + public UndoRedoManager getUndoManager() { return categoryUndoHistory; } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizationChangeSet.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizationChangeSet.java index e9b688cf83..5c7f6d63d5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizationChangeSet.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizationChangeSet.java @@ -19,14 +19,16 @@ package org.sleuthkit.autopsy.imagegallery.actions; import com.google.common.collect.ImmutableMap; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.datamodel.TagName; /** * */ -final public class CategorizationChangeSet { +final class CategorizationChangeSet implements Command { private final TagName newCategory; final private Map oldCategories = new HashMap<>(); @@ -46,4 +48,25 @@ final public class CategorizationChangeSet { Map getOldCategories() { return ImmutableMap.copyOf(oldCategories); } + + /** + * + * @param controller the value of controller + */ + public void apply(final ImageGalleryController controller) { + CategorizeAction categorizeAction = new CategorizeAction(controller); + categorizeAction.addTagsToFiles(newCategory, "", this.oldCategories.keySet(), false); + + } + + /** + * + * @param controller the value of controller + */ + public void undo(final ImageGalleryController controller) { + CategorizeAction categorizeAction = new CategorizeAction(controller); + for (Map.Entry entry : this.getOldCategories().entrySet()) { + categorizeAction.addTagsToFiles(entry.getValue(), "", Collections.singleton(entry.getKey()), false); + } + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java index b948b88ece..124a00d954 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java @@ -28,8 +28,8 @@ import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; +import javax.annotation.Nonnull; import javax.swing.JOptionPane; -import org.sleuthkit.autopsy.coreutils.History; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; @@ -52,12 +52,12 @@ public class CategorizeAction extends AddTagAction { private static final Logger LOGGER = Logger.getLogger(CategorizeAction.class.getName()); private final ImageGalleryController controller; - private final History undoHistory; + private final UndoRedoManager undoManager; public CategorizeAction(ImageGalleryController controller) { super(); this.controller = controller; - undoHistory = controller.getUndoHistory(); + undoManager = controller.getUndoManager(); } public Menu getPopupMenu() { @@ -112,14 +112,16 @@ public class CategorizeAction extends AddTagAction { private class CategorizeTask extends ImageGalleryController.InnerTask { private final Set fileIDs; + @Nonnull private final TagName tagName; private final String comment; private final CategorizationChangeSet categorizationChangeSet; private final boolean createUndo; - public CategorizeTask(Set fileIDs, TagName tagName, String comment, boolean createUndo) { + public CategorizeTask(Set fileIDs, @Nonnull TagName tagName, String comment, boolean createUndo) { super(); this.fileIDs = fileIDs; + java.util.Objects.requireNonNull(tagName); this.tagName = tagName; this.comment = comment; this.createUndo = createUndo; @@ -138,7 +140,9 @@ public class CategorizeAction extends AddTagAction { if (createUndo) { Category oldCat = file.getCategory(); TagName oldCatTagName = categoryManager.getTagName(oldCat); - categorizationChangeSet.add(fileID, oldCatTagName); + if (false == tagName.equals(oldCatTagName)) { + categorizationChangeSet.add(fileID, oldCatTagName); + } } final List fileTags = tagsManager.getContentTagsByContent(file); @@ -168,8 +172,8 @@ public class CategorizeAction extends AddTagAction { } } - if (createUndo) { - undoHistory.advance(categorizationChangeSet); + if (createUndo && categorizationChangeSet.getOldCategories().isEmpty() == false) { + undoManager.addToUndo(categorizationChangeSet); } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Command.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Command.java new file mode 100644 index 0000000000..3be0d5429e --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Command.java @@ -0,0 +1,39 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.imagegallery.actions; + +import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; + +/** + */ +public interface Command { + + /** + * + * @param controller the value of controller + */ + void apply(final ImageGalleryController controller); + + /** + * + * @param controller the value of controller + */ + void undo(final ImageGalleryController controller); + +} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Redo.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Redo.java new file mode 100644 index 0000000000..e5d161ccc1 --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Redo.java @@ -0,0 +1,42 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.imagegallery.actions; + +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import org.controlsfx.control.action.Action; +import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; + +/** + * + */ +public class Redo extends Action { + + private static final Image REDO_IMAGE = new Image("/org/sleuthkit/autopsy/imagegallery/images/redo.png", 16, 16, true, true, true); + + public Redo(ImageGalleryController controller) { + super("Redo"); + setGraphic(new ImageView(REDO_IMAGE)); + setAccelerator(new KeyCodeCombination(KeyCode.Y, KeyCodeCombination.CONTROL_DOWN)); + setEventHandler(actionEvent -> controller.getUndoManager().redo()); + disabledProperty().bind(controller.getUndoManager().redosAvailableProporty().lessThanOrEqualTo(0)); + } +} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Undo.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Undo.java index ed0ce72858..5bb188400b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Undo.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/Undo.java @@ -18,27 +18,25 @@ */ package org.sleuthkit.autopsy.imagegallery.actions; -import java.util.Collections; -import java.util.Map; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; import org.controlsfx.control.action.Action; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; -import org.sleuthkit.datamodel.TagName; /** * */ public class Undo extends Action { - + + private static final Image UNDO_IMAGE = new Image("/org/sleuthkit/autopsy/imagegallery/images/undo.png", 16, 16, true, true, true); + public Undo(ImageGalleryController controller) { super("Undo"); - - setEventHandler(actionEvent -> { - CategorizeAction categorizeAction = new CategorizeAction(controller); - CategorizationChangeSet retreat = controller.getUndoHistory().getCurrentState(); - for (Map.Entry entry : retreat.getOldCategories().entrySet()) { - categorizeAction.addTagsToFiles(entry.getValue(), "", Collections.singleton(entry.getKey()), false); - } - controller.getUndoHistory().retreat(); - }); + setGraphic(new ImageView(UNDO_IMAGE)); + setAccelerator(new KeyCodeCombination(KeyCode.Z, KeyCodeCombination.CONTROL_DOWN)); + setEventHandler(actionEvent -> controller.getUndoManager().undo()); + disabledProperty().bind(controller.getUndoManager().undosAvailableProperty().lessThanOrEqualTo(0)); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/UndoRedoManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/UndoRedoManager.java new file mode 100644 index 0000000000..cc5b0a098d --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/UndoRedoManager.java @@ -0,0 +1,153 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.imagegallery.actions; + +import java.util.Objects; +import java.util.Optional; +import javafx.beans.property.ReadOnlyIntegerProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.FXCollections; +import javax.annotation.Nonnull; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; +import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; + +/** + * + */ +@ThreadSafe +public class UndoRedoManager { + + @GuardedBy("this") + private final ObservableStack undoStack = new ObservableStack<>(); + + @GuardedBy("this") + private final ObservableStack redoStack = new ObservableStack<>(); + + private final ImageGalleryController controller; + + synchronized public int getRedosAvaialable() { + return redoStack.getSize(); + } + + synchronized public int getUndosAvailable() { + return redoStack.getSize(); + } + + synchronized public ReadOnlyIntegerProperty redosAvailableProporty() { + return redoStack.sizeProperty(); + } + + synchronized public ReadOnlyIntegerProperty undosAvailableProperty() { + return undoStack.sizeProperty(); + } + + public UndoRedoManager(ImageGalleryController controller) { + this.controller = controller; + } + + synchronized public void reset() { + redoStack.clear(); + undoStack.clear(); + } + + /** + * Flip the top redo command over to the undo stack + * + * @return the redone command or null if there are no redos available + */ + synchronized public Optional redo() { + if (redoStack.isEmpty()) { + return Optional.empty(); + } else { + Command pop = redoStack.pop(); + undoStack.push(pop); + pop.apply(controller); + return Optional.of(pop); + } + + } + + /** + * Flip the top undo command over to the redo stack + * + * @return the undone command or null if there there are no undos available. + */ + synchronized public Optional undo() { + if (undoStack.isEmpty()) { + return Optional.empty(); + } else { + final Command pop = undoStack.pop(); + redoStack.push(pop); + pop.undo(controller); + return Optional.of(pop); + } + } + + /** + * push a new command onto the undo stack and clear the redo stack + * + * @param command the command to add to the undo stack + */ + synchronized public void addToUndo(@Nonnull Command command) { + Objects.requireNonNull(command); + undoStack.push(command); + redoStack.clear(); + } + + /** + * A simple extension to SimpleListProperty to add a stack api + * + * TODO: this really should not extend SimpleListProperty but should + * delegate to an appropriate observable implementation while implementing + * the {@link Deque} interface + */ + private static class ObservableStack extends SimpleListProperty { + + ObservableStack() { + super(FXCollections.synchronizedObservableList(FXCollections.observableArrayList())); + } + + public void push(T item) { + synchronized (this) { + add(0, item); + } + } + + public T pop() { + synchronized (this) { + if (isEmpty()) { + return null; + } else { + return remove(0); + } + } + } + + public T peek() { + synchronized (this) { + if (isEmpty()) { + return null; + } else { + return get(0); + } + } + } + } +} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml index 0328f4b8d7..0844568c07 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml @@ -134,9 +134,11 @@ + +