diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index dbf974fc55..f961ce5113 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -19,8 +19,8 @@ package org.sleuthkit.autopsy.imagegallery.actions; import java.util.Optional; -import javafx.beans.binding.Bindings; -import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.Observable; +import javafx.beans.binding.ObjectExpression; import javafx.event.ActionEvent; import javafx.scene.image.ImageView; import org.controlsfx.control.action.Action; @@ -31,24 +31,43 @@ import org.sleuthkit.autopsy.imagegallery.grouping.GroupViewState; * */ public class NextUnseenGroup extends Action { - + private final ImageGalleryController controller; - + public NextUnseenGroup(ImageGalleryController controller) { super("Next Unseen group"); this.controller = controller; setGraphic(new ImageView("/org/sleuthkit/autopsy/imagegallery/images/control-double.png")); - disabledProperty().bind(Bindings.isEmpty(controller.getGroupManager().getUnSeenGroups())); - + + controller.getGroupManager().getUnSeenGroups().addListener((Observable observable) -> { + updateDisabledStatus(); + }); + setEventHandler((ActionEvent t) -> { Optional.ofNullable(controller.viewState()) - .map((ReadOnlyObjectProperty t1) -> t1.get()) + .map(ObjectExpression::getValue) .map(GroupViewState::getGroup) .ifPresent(controller.getGroupManager()::markGroupSeen); - - if (controller.getGroupManager().getUnSeenGroups().isEmpty() == false) { + + if (controller.getGroupManager().getUnSeenGroups().size() <= 1) { + setText("Mark Group Seen"); + setGraphic(new ImageView("/org/sleuthkit/autopsy/imagegallery/images/control-stop.png")); + if (!controller.getGroupManager().getUnSeenGroups().isEmpty()) { + controller.advance(GroupViewState.tile(controller.getGroupManager().getUnSeenGroups().get(0))); + } + } else { + setText("Next Unseen group"); + setGraphic(new ImageView("/org/sleuthkit/autopsy/imagegallery/images/control-double.png")); + setDisabled(false); controller.advance(GroupViewState.tile(controller.getGroupManager().getUnSeenGroups().get(0))); } + updateDisabledStatus(); }); + + updateDisabledStatus(); + } + + private void updateDisabledStatus() { + disabledProperty().set(controller.getGroupManager().getUnSeenGroups().isEmpty()); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java index e771797aaa..0a66d1b868 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.imagegallery.grouping; import java.util.List; import java.util.Objects; import java.util.logging.Level; +import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.sleuthkit.autopsy.coreutils.Logger; @@ -35,7 +36,6 @@ public class DrawableGroup implements Comparable { private static final Logger LOGGER = Logger.getLogger(DrawableGroup.class.getName()); - public static String getBlankGroupName() { return "unknown"; } @@ -44,6 +44,7 @@ public class DrawableGroup implements Comparable { //cache the number of files in this groups with hashset hits private int hashSetHitsCount = -1; + private final ReadOnlyBooleanWrapper seen = new ReadOnlyBooleanWrapper(false); synchronized public ObservableList fileIds() { return fileIDs; @@ -148,4 +149,16 @@ public class DrawableGroup implements Comparable { public int compareTo(DrawableGroup other) { return this.groupKey.getValueDisplayName().compareTo(other.groupKey.getValueDisplayName()); } + + void setSeen() { + this.seen.set(true); + } + + public ReadOnlyBooleanWrapper seenProperty() { + return seen; + } + + public boolean isSeen() { + return seen.get(); + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java index ea20263b18..ebda117c24 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java @@ -81,18 +81,27 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { /** * map from {@link GroupKey}s to {@link DrawableGroup}s. All groups (even - * not fully analyzed or not visible groups) could be in this map + * not + * fully analyzed or not visible groups could be in this map */ private final Map, DrawableGroup> groupMap = new HashMap<>(); - /** list of all analyzed groups */ + /** + * list of all analyzed groups + */ @ThreadConfined(type = ThreadType.JFX) private final ObservableList analyzedGroups = FXCollections.observableArrayList(); - /** list of unseen groups */ + private final ObservableList publicAnalyzedGroupsWrapper = FXCollections.unmodifiableObservableList(analyzedGroups); + /** + * list of unseen groups + */ @ThreadConfined(type = ThreadType.JFX) private final ObservableList unSeenGroups = FXCollections.observableArrayList(); +// private final SortedList sortedUnSeenGroups = new SortedList<>(unSeenGroups); + private final ObservableList publicSortedUnseenGroupsWrapper = FXCollections.unmodifiableObservableList(unSeenGroups); + private ReGroupTask groupByTask; /* --- current grouping/sorting attributes --- */ @@ -101,7 +110,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { private volatile DrawableAttribute groupBy = DrawableAttribute.PATH; private volatile SortOrder sortOrder = SortOrder.ASCENDING; - private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper(); + private ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper(); public void setDB(DrawableDB db) { this.db = db; @@ -110,13 +119,12 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { } public ObservableList getAnalyzedGroups() { - return FXCollections.unmodifiableObservableList(analyzedGroups); + return publicAnalyzedGroupsWrapper; } @ThreadConfined(type = ThreadType.JFX) public ObservableList getUnSeenGroups() { - return FXCollections.unmodifiableObservableList(unSeenGroups); - + return publicSortedUnseenGroupsWrapper; } /** @@ -253,7 +261,9 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { * @param group the {@link DrawableGroup} to mark as seen */ public void markGroupSeen(DrawableGroup group) { + db.markGroupSeen(group.getGroupKey()); + group.setSeen(); synchronized (unSeenGroups) { unSeenGroups.remove(group); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeCell.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeCell.java index 8e28fd91f7..51925f27ab 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeCell.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeCell.java @@ -50,7 +50,8 @@ class GroupTreeCell extends TreeCell { * reference to listener that allows us to remove it from a group when a new * group is assigned to this Cell */ - private InvalidationListener listener; + private InvalidationListener fileListener; + private InvalidationListener seenListener; public GroupTreeCell() { //TODO: move this to .css file @@ -70,8 +71,9 @@ class GroupTreeCell extends TreeCell { if (Objects.nonNull(listener)) { Optional.ofNullable(getItem()) .map(TreeNode::getGroup) - .ifPresent((DrawableGroup t) -> { - t.fileIds().removeListener(listener); + .ifPresent(group -> { + group.fileIds().removeListener(fileListener); + group.seenProperty().removeListener(seenListener); }); } @@ -82,6 +84,7 @@ class GroupTreeCell extends TreeCell { setTooltip(null); setText(null); setGraphic(null); + setStyle(""); }); } else { final String groupName = StringUtils.defaultIfBlank(tNode.getPath(), DrawableGroup.getBlankGroupName()); @@ -92,30 +95,45 @@ class GroupTreeCell extends TreeCell { setTooltip(new Tooltip(groupName)); setText(groupName); setGraphic(new ImageView(EMPTY_FOLDER_ICON)); + setStyle(""); }); } else { - listener = (Observable o) -> { + fileListener = (Observable o) -> { final String countsText = getCountsText(); Platform.runLater(() -> { setText(groupName + countsText); }); }; //if number of files in this group changes (eg file is recategorized), update counts via listener - tNode.getGroup().fileIds().addListener(listener); + tNode.getGroup().fileIds().addListener(fileListener); + + seenListener = (Observable observable) -> { + final String style = getSeenStyle(); + Platform.runLater(() -> { + setStyle(style); + }); + }; + tNode.getGroup().seenProperty().addListener(seenListener); //... and use icon corresponding to group type final Image icon = tNode.getGroup().groupKey.getAttribute().getIcon(); final String countsText = getCountsText(); + final String style = getSeenStyle(); Platform.runLater(() -> { setTooltip(new Tooltip(groupName)); setGraphic(new ImageView(icon)); setText(groupName + countsText); + setStyle(style); }); } } } + private String getSeenStyle() { + return getItem().getGroup().isSeen() ? "" : "-fx-font-weight:bold;"; + } + private synchronized String getCountsText() { final String counts = Optional.ofNullable(getItem()) .map(TreeNode::getGroup) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/control-stop-000-small.png b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/control-stop-000-small.png new file mode 100644 index 0000000000..ad5f34b835 Binary files /dev/null and b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/control-stop-000-small.png differ diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/control-stop.png b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/control-stop.png new file mode 100644 index 0000000000..8c63819768 Binary files /dev/null and b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/control-stop.png differ