mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-14 17:06:16 +00:00
use new UndoRedoManager and Command interface; add Redo and improve UX (icons)
This commit is contained in:
parent
d9fb562ab1
commit
d9878e2b5d
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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">
|
||||
|
@ -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 |
Loading…
x
Reference in New Issue
Block a user