use new UndoRedoManager and Command interface; add Redo and improve UX (icons)

This commit is contained in:
jmillman 2016-01-06 14:50:57 -05:00
parent d9fb562ab1
commit d9878e2b5d
11 changed files with 292 additions and 25 deletions

View File

@ -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<GroupViewState> historyManager = new History<>();
private final History<CategorizationChangeSet> 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<CategorizationChangeSet> getUndoHistory() {
public UndoRedoManager getUndoManager() {
return categoryUndoHistory;
}

View File

@ -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<Long, TagName> oldCategories = new HashMap<>();
@ -46,4 +48,25 @@ final public class CategorizationChangeSet {
Map<Long, TagName> 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<Long, TagName> entry : this.getOldCategories().entrySet()) {
categorizeAction.addTagsToFiles(entry.getValue(), "", Collections.singleton(entry.getKey()), false);
}
}
}

View File

@ -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<CategorizationChangeSet> 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<Long> fileIDs;
@Nonnull
private final TagName tagName;
private final String comment;
private final CategorizationChangeSet categorizationChangeSet;
private final boolean createUndo;
public CategorizeTask(Set<Long> fileIDs, TagName tagName, String comment, boolean createUndo) {
public CategorizeTask(Set<Long> 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<ContentTag> 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);
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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);
}

View File

@ -0,0 +1,42 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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));
}
}

View File

@ -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<Long, TagName> 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));
}
}

View File

@ -0,0 +1,153 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<Command> undoStack = new ObservableStack<>();
@GuardedBy("this")
private final ObservableStack<Command> 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<Command> 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<Command> 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<T> extends SimpleListProperty<T> {
ObservableStack() {
super(FXCollections.<T>synchronizedObservableList(FXCollections.<T>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);
}
}
}
}
}

View File

@ -134,9 +134,11 @@
<Insets left="5.0" />
</padding>
</HBox>
<Separator prefWidth="30.0" />
<Button fx:id="undoButton" mnemonicParsing="false" text="Undo" />
<Button fx:id="redoButton" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" text="Redo" />
<Region fx:id="spacer" prefHeight="-1.0" prefWidth="-1.0" />
<Separator prefWidth="30.0" />
<Button fx:id="undoButton" mnemonicParsing="false" text="Undo" />
<SegmentedButton fx:id="segButton">
<buttons>
<ToggleButton id="" fx:id="tileToggle" alignment="CENTER" contentDisplay="GRAPHIC_ONLY" graphicTextGap="0.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="32.0" mnemonicParsing="false" prefWidth="-1.0" scaleX="1.0" selected="true" text="" textAlignment="CENTER">

View File

@ -116,6 +116,7 @@ import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeSelectedFilesAction;
import org.sleuthkit.autopsy.imagegallery.actions.Forward;
import org.sleuthkit.autopsy.imagegallery.actions.NextUnseenGroup;
import org.sleuthkit.autopsy.imagegallery.actions.Redo;
import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter;
import org.sleuthkit.autopsy.imagegallery.actions.TagSelectedFilesAction;
import org.sleuthkit.autopsy.imagegallery.actions.Undo;
@ -162,6 +163,8 @@ public class GroupPane extends BorderPane {
@FXML
private Button undoButton;
@FXML
private Button redoButton;
@FXML
private SplitMenuButton catSelectedSplitMenu;
@ -224,6 +227,7 @@ public class GroupPane extends BorderPane {
private Integer selectionAnchorIndex;
private final Undo undoAction;
private final Redo redoAction;
GroupViewMode getGroupViewMode() {
return groupViewMode.get();
@ -267,6 +271,7 @@ public class GroupPane extends BorderPane {
backAction = new Back(controller);
forwardAction = new Forward(controller);
undoAction = new Undo(controller);
redoAction = new Redo(controller);
FXMLConstructor.construct(this, "GroupPane.fxml");
}
@ -576,6 +581,7 @@ public class GroupPane extends BorderPane {
});
ActionUtils.configureButton(undoAction, undoButton);
ActionUtils.configureButton(redoAction, redoButton);
ActionUtils.configureButton(forwardAction, forwardButton);
ActionUtils.configureButton(backAction, backButton);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB