Merge branch 'develop' of https://github.com/sleuthkit/autopsy into vm_detection

This commit is contained in:
Eugene Livis 2016-01-27 13:00:14 -05:00
commit 013a85919f
16 changed files with 760 additions and 399 deletions

View File

@ -43,7 +43,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
private static final Logger LOGGER = Logger.getLogger(AbstractAbstractFileNode.class.getName());
/**
* @param <T> type of the AbstractFile data to encapsulate
* @param <T> type of the AbstractFile data to encapsulate
* @param abstractFile file to encapsulate
*/
AbstractAbstractFileNode(T abstractFile) {
@ -52,7 +52,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
int dotIndex = name.lastIndexOf(".");
if (dotIndex > 0) {
String ext = name.substring(dotIndex).toLowerCase();
// If this is an archive file we will listen for ingest events
// that will notify us when new content has been identified.
for (String s : FileTypeExtensions.getArchiveExtensions()) {
@ -228,6 +228,13 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
public String toString() {
return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.objectId");
}
},
MIMETYPE {
@Override
public String toString() {
return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.mimeType");
}
},
}
@ -235,8 +242,8 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
/**
* Fill map with AbstractFile properties
*
* @param map map with preserved ordering, where property names/values
* are put
* @param map map with preserved ordering, where property names/values are
* put
* @param content to extract properties from
*/
public static void fillPropertyMap(Map<String, Object> map, AbstractFile content) {
@ -268,6 +275,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
map.put(AbstractFilePropertyType.HASHSETS.toString(), getHashSetHitsForFile(content));
map.put(AbstractFilePropertyType.MD5HASH.toString(), content.getMd5Hash() == null ? "" : content.getMd5Hash());
map.put(AbstractFilePropertyType.ObjectID.toString(), content.getId());
map.put(AbstractFilePropertyType.MIMETYPE.toString(), content.getMIMEType() == null ? "" : content.getMIMEType());
}
static String getContentDisplayName(AbstractFile file) {

View File

@ -18,6 +18,7 @@ AbstractAbstractFileNode.typeMetaColLbl=Type(Meta)
AbstractAbstractFileNode.knownColLbl=Known
AbstractAbstractFileNode.inHashsetsColLbl=In Hashsets
AbstractAbstractFileNode.md5HashColLbl=MD5 Hash
AbstractAbstractFileNode.mimeType = MIME Type
AbstractContentChildren.CreateTSKNodeVisitor.exception.noNodeMsg=No Node defined for the given SleuthkitItem
AbstractContentChildren.createAutopsyNodeVisitor.exception.noNodeMsg=No Node defined for the given DisplayableItem
AbstractContentNode.exception.cannotChangeSysName.msg=Cannot change the system name.

View File

@ -23,9 +23,9 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* This class supports programmer productivity by abstracting frequently used
* code to load FXML-defined GUI components,
@ -35,10 +35,10 @@ import org.sleuthkit.autopsy.coreutils.Logger;
* http://stackoverflow.com/questions/11734885/javafx2-very-poor-performance-when-adding-custom-made-fxmlpanels-to-gridpane.
*/
public class FXMLConstructor {
private static Logger logger = Logger.getLogger(FXMLConstructor.class.getName());
static public void construct(Node n, String fxmlFileName) {
static public void construct(Object n, String fxmlFileName) {
final String name = "nbres:/" + StringUtils.replace(n.getClass().getPackage().getName(), ".", "/") + "/" + fxmlFileName;
try {

View File

@ -75,7 +75,6 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.NavPanel;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
@ -94,6 +93,7 @@ import org.sleuthkit.datamodel.TskData;
public final class ImageGalleryController implements Executor {
private final Executor execDelegate = Executors.newSingleThreadExecutor();
private Runnable showTree;
@Override
public void execute(Runnable command) {
@ -153,7 +153,7 @@ public final class ImageGalleryController implements Executor {
private Node infoOverlay;
private SleuthkitCase sleuthKitCase;
private NavPanel navPanel;
// private NavPanel navPanel;
public ReadOnlyBooleanProperty getMetaDataCollapsed() {
return metaDataCollapsed.getReadOnlyProperty();
@ -266,8 +266,8 @@ public final class ImageGalleryController implements Executor {
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
public void advance(GroupViewState newState, boolean forceShowTree) {
if (Objects.nonNull(navPanel) && forceShowTree) {
navPanel.showTree();
if (forceShowTree && showTree != null) {
showTree.run();
}
historyManager.advance(newState);
}
@ -482,8 +482,8 @@ public final class ImageGalleryController implements Executor {
return tagsManager;
}
public void setNavPanel(NavPanel navPanel) {
this.navPanel = navPanel;
public void setShowTree(Runnable showTree) {
this.showTree = showTree;
}
public UndoRedoManager getUndoManager() {

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -23,7 +23,9 @@ import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TabPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.netbeans.api.settings.ConvertAsProperties;
@ -40,7 +42,8 @@ import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.GroupPane;
import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.MetaDataPane;
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.NavPanel;
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.GroupTree;
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.HashHitGroupList;
/**
* Top component which displays ImageGallery interface.
@ -92,7 +95,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
}
public static void closeTopComponent() {
if(topComponentInitialized){
if (topComponentInitialized) {
final TopComponent etc = WindowManager.getDefault().findTopComponent("ImageGalleryTopComponent");
if (etc != null) {
try {
@ -122,7 +125,8 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
private GroupPane groupPane;
private NavPanel navPanel;
private GroupTree groupTree;
private HashHitGroupList hashHitList;
private VBox leftPane;
@ -132,7 +136,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
setName(Bundle.CTL_ImageGalleryTopComponent());
setToolTipText(Bundle.HINT_ImageGalleryTopComponent());
initComponents();
Platform.runLater(() -> {//initialize jfx ui
@ -149,8 +153,13 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
metaDataTable = new MetaDataPane(controller);
navPanel = new NavPanel(controller);
leftPane = new VBox(navPanel, new SummaryTablePane(controller));
groupTree = new GroupTree(controller);
hashHitList = new HashHitGroupList(controller);
TabPane tabPane = new TabPane(groupTree, hashHitList);
VBox.setVgrow(tabPane, Priority.ALWAYS);
leftPane = new VBox(tabPane, new SummaryTablePane(controller));
SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
@ -158,7 +167,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
splitPane.setDividerPositions(0.0, 1.0);
ImageGalleryController.getDefault().setStacks(fullUIStack, centralStack);
ImageGalleryController.getDefault().setNavPanel(navPanel);
ImageGalleryController.getDefault().setShowTree(() -> tabPane.getSelectionModel().select(groupTree));
});
}

View File

@ -39,6 +39,8 @@ import java.util.stream.Stream;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import static javafx.concurrent.Worker.State.CANCELLED;
@ -116,10 +118,13 @@ public class GroupManager {
* --- current grouping/sorting attributes ---
*/
private volatile GroupSortBy sortBy = GroupSortBy.NONE;
private volatile DrawableAttribute<?> groupBy = DrawableAttribute.PATH;
private volatile SortOrder sortOrder = SortOrder.ASCENDING;
private final ReadOnlyObjectWrapper<GroupSortBy> sortByProp = new ReadOnlyObjectWrapper<>(sortBy);
private final ReadOnlyObjectWrapper< DrawableAttribute<?>> groupByProp = new ReadOnlyObjectWrapper<>(groupBy);
private final ReadOnlyObjectWrapper<SortOrder> sortOrderProp = new ReadOnlyObjectWrapper<>(sortOrder);
private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper();
public void setDB(DrawableDB db) {
@ -424,24 +429,39 @@ public class GroupManager {
return sortBy;
}
public void setSortBy(GroupSortBy sortBy) {
void setSortBy(GroupSortBy sortBy) {
this.sortBy = sortBy;
Platform.runLater(() -> sortByProp.set(sortBy));
}
public ReadOnlyObjectProperty<GroupSortBy> getSortByProperty() {
return sortByProp.getReadOnlyProperty();
}
public DrawableAttribute<?> getGroupBy() {
return groupBy;
}
public void setGroupBy(DrawableAttribute<?> groupBy) {
void setGroupBy(DrawableAttribute<?> groupBy) {
this.groupBy = groupBy;
Platform.runLater(() -> groupByProp.set(groupBy));
}
public ReadOnlyObjectProperty<DrawableAttribute<?>> getGroupByProperty() {
return groupByProp.getReadOnlyProperty();
}
public SortOrder getSortOrder() {
return sortOrder;
}
public void setSortOrder(SortOrder sortOrder) {
void setSortOrder(SortOrder sortOrder) {
this.sortOrder = sortOrder;
Platform.runLater(() -> sortOrderProp.set(sortOrder));
}
public ReadOnlyObjectProperty<SortOrder> getSortOrderProperty() {
return sortOrderProp.getReadOnlyProperty();
}
/**

View File

@ -0,0 +1,82 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-16 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.gui.navpanel;
import com.google.common.collect.ImmutableList;
import java.util.Comparator;
import java.util.function.Function;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
/**
*
*/
final class GroupComparators<T extends Comparable<T>> implements Comparator<DrawableGroup> {
static final GroupComparators<Long> UNCATEGORIZED_COUNT =
new GroupComparators<>("Uncategorized Count", DrawableGroup::getUncategorizedCount, String::valueOf, false);
static final GroupComparators<String> ALPHABETICAL =
new GroupComparators<>("Group Name", DrawableGroup::getGroupByValueDislpayName, String::valueOf, false);
static final GroupComparators<Long> HIT_COUNT =
new GroupComparators<>("Hit Count", DrawableGroup::getHashSetHitsCount, String::valueOf, true);
static final GroupComparators<Integer> FILE_COUNT =
new GroupComparators<>("Group Size", DrawableGroup::getSize, String::valueOf, true);
static final GroupComparators<Double> HIT_FILE_RATIO =
new GroupComparators<>("Hit Density", DrawableGroup::getHashHitDensity, density -> String.format("%.2f", density) + "%", true);
private final static ImmutableList<GroupComparators<?>> values = ImmutableList.of(UNCATEGORIZED_COUNT, ALPHABETICAL, HIT_COUNT, FILE_COUNT, HIT_FILE_RATIO);
public static ImmutableList<GroupComparators<?>> getValues() {
return values;
}
private final Function<DrawableGroup, T> extractor;
private final Function<T, String> valueFormatter;
private final boolean orderReveresed;
private final String displayName;
private GroupComparators(String displayName, Function<DrawableGroup, T> extractor, Function<T, String> formatter, boolean defaultOrderReversed) {
this.displayName = displayName;
this.extractor = extractor;
this.orderReveresed = defaultOrderReversed;
this.valueFormatter = formatter;
}
@Override
public int compare(DrawableGroup o1, DrawableGroup o2) {
int compareTo = extractor.apply(o1).compareTo(extractor.apply(o2));
return orderReveresed ? -compareTo : compareTo;
}
public String getDisplayName() {
return displayName;
}
@Override
public String toString() {
return displayName;
}
String getFormattedValueOfGroup(DrawableGroup group) {
return valueFormatter.apply(extractor.apply(group));
}
}

View File

@ -0,0 +1,177 @@
/*
* 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.gui.navpanel;
import static java.util.Objects.isNull;
import java.util.Optional;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.control.ListCell;
import javafx.scene.control.OverrunStyle;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javax.annotation.Nonnull;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
/**
*
*/
class GroupListCell extends ListCell<DrawableGroup> {
/**
* icon to use if this cell's TreeNode doesn't represent a group but just a
* folder(with no DrawableFiles) in the file system hierarchy.
*/
private static final Image EMPTY_FOLDER_ICON =
new Image(GroupTreeCell.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/folder.png"));
/**
* reference to group files listener that allows us to remove it from a
* group when a new group is assigned to this Cell
*/
private final InvalidationListener fileCountListener = (Observable o) -> {
final String text = getGroupName() + getCountsText();
Platform.runLater(() -> {
setText(text);
setTooltip(new Tooltip(text));
});
};
/**
* reference to group seen listener that allows us to remove it from a group
* when a new group is assigned to this Cell
*/
private final InvalidationListener seenListener = (Observable o) -> {
final String style = getSeenStyleClass();
Platform.runLater(() -> {
setStyle(style);
});
};
private final ReadOnlyObjectProperty<GroupComparators<?>> sortOrder;
GroupListCell(ReadOnlyObjectProperty<GroupComparators<?>> sortOrderProperty) {
this.sortOrder = sortOrderProperty;
getStylesheets().add(GroupTreeCell.class.getResource("GroupTreeCell.css").toExternalForm());
getStyleClass().add("groupTreeCell"); //reduce indent to 5, default is 10 which uses up a lot of space.
//since end of path is probably more interesting put ellipsis at front
setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
Platform.runLater(() -> {
prefWidthProperty().bind(getListView().widthProperty().subtract(15));
});
}
/**
* {@inheritDoc }
*/
@Override
protected synchronized void updateItem(final DrawableGroup group, boolean empty) {
//if there was a previous group, remove the listeners
Optional.ofNullable(getItem())
.ifPresent(oldGroup -> {
sortOrder.removeListener(fileCountListener);
oldGroup.fileIds().removeListener(fileCountListener);
oldGroup.seenProperty().removeListener(seenListener);
oldGroup.uncatCountProperty().removeListener(fileCountListener);
});
super.updateItem(group, empty);
if (isNull(group) || empty) {
Platform.runLater(() -> {
setTooltip(null);
setText(null);
setGraphic(null);
setStyle("");
});
} else {
if (isNull(group)) {
final String text = getGroupName();
//"dummy" group in file system tree <=> a folder with no drawables
Platform.runLater(() -> {
setTooltip(new Tooltip(text));
setText(text);
setGraphic(new ImageView(EMPTY_FOLDER_ICON));
setStyle("");
});
} else {
//if number of files in this group changes (eg a file is recategorized), update counts via listener
group.fileIds().addListener(fileCountListener);
group.uncatCountProperty().addListener(fileCountListener);
sortOrder.addListener(fileCountListener);
//if the seen state of this group changes update its style
group.seenProperty().addListener(seenListener);
//and use icon corresponding to group type
final Image icon = group.groupKey.getAttribute().getIcon();
final String text = getGroupName() + getCountsText();
final String style = getSeenStyleClass();
Platform.runLater(() -> {
setTooltip(new Tooltip(text));
setGraphic(new ImageView(icon));
setText(text);
setStyle(style);
});
}
}
}
private String getGroupName() {
return Optional.ofNullable(getItem())
.map(group -> group.getGroupByValueDislpayName())
.orElse("");
}
/**
* return the styleClass to apply based on the assigned group's seen status
*
* @return the style class to apply
*/
@Nonnull
private String getSeenStyleClass() {
return Optional.ofNullable(getItem())
.map(DrawableGroup::isSeen)
.map(seen -> seen ? "" : "-fx-font-weight:bold;")
.orElse(""); //if item is null or group is null
}
/**
* get the counts part of the text to apply to this cell, including
* parentheses
*
* @return get the counts part of the text to apply to this cell
*/
@Nonnull
private String getCountsText() {
return Optional.ofNullable(getItem())
.map(group ->
" (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
? group.getSize()
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
).orElse(""); //if item is null or group is null
}
}

View File

@ -0,0 +1,149 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2016 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.gui.navpanel;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.ImageView;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
/**
* Shows path based groups as a tree and others kinds of groups as a flat list (
* a tree with an invisible root and only one level of children). Shows controls
* to adjust the sorting only in flat list mode.
*/
final public class GroupTree extends NavPanel<TreeItem<GroupTreeNode>> {
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private final GroupTreeItem groupTreeRoot = new GroupTreeItem("", null, true);
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private final TreeView<GroupTreeNode> groupTree = new TreeView<>(groupTreeRoot);
public GroupTree(ImageGalleryController controller) {
super(controller);
FXMLConstructor.construct(this, "NavPanel.fxml");
}
@FXML
@Override
void initialize() {
super.initialize();
setText("All Groups");
setGraphic(new ImageView("org/sleuthkit/autopsy/imagegallery/images/Folder-icon.png"));
getBorderPane().setCenter(groupTree);
//only show sorting controls if not grouping by path
BooleanBinding groupedByPath = Bindings.equal(getGroupManager().getGroupByProperty(), DrawableAttribute.PATH);
getToolBar().visibleProperty().bind(groupedByPath.not());
getToolBar().managedProperty().bind(groupedByPath.not());
groupTree.setCellFactory(treeView -> new GroupTreeCell(getSortByBox().getSelectionModel().selectedItemProperty()));
groupTree.setShowRoot(false);
getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
while (change.next()) {
change.getAddedSubList().stream().forEach(this::insertGroup);
change.getRemoved().stream().forEach(this::removeFromTree);
}
});
for (DrawableGroup g : getGroupManager().getAnalyzedGroups()) {
insertGroup(g);
}
}
/**
* Set the tree to the passed in group
*
* @param grouping
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@Override
void setFocusedGroup(DrawableGroup grouping) {
final GroupTreeItem treeItemForGroup = groupTreeRoot.getTreeItemForPath(groupingToPath(grouping));
if (treeItemForGroup != null) {
groupTree.getSelectionModel().select(treeItemForGroup);
Platform.runLater(() -> {
int row = groupTree.getRow(treeItemForGroup);
if (row != -1) {
groupTree.scrollTo(row - 2); //put newly selected row 3 from the top
}
});
}
}
@Override
Function<TreeItem<GroupTreeNode>, DrawableGroup> getDataItemMapper() {
return treeItem -> treeItem.getValue().getGroup();
}
@Override
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
SelectionModel<TreeItem<GroupTreeNode>> getSelectionModel() {
return groupTree.getSelectionModel();
}
@Override
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void applyGroupComparator() {
groupTreeRoot.resortChildren(getComparator());
}
@Override
GroupComparators<String> getDefaultComparator() {
return GroupComparators.ALPHABETICAL;
}
private void insertGroup(DrawableGroup g) {
groupTreeRoot.insert(groupingToPath(g), g, true);
}
private void removeFromTree(DrawableGroup g) {
Optional.ofNullable(groupTreeRoot.getTreeItemForGroup(g))
.ifPresent(GroupTreeItem::removeFromParent);
}
private static List<String> groupingToPath(DrawableGroup g) {
String path = g.groupKey.getValueDisplayName();
if (g.groupKey.getAttribute() != DrawableAttribute.PATH) {
String stripStart = StringUtils.strip(path, "/");
return Arrays.asList(stripStart);
} else {
String[] cleanPathTokens = StringUtils.stripStart(path, "/").split("/");
return Arrays.asList(cleanPathTokens);
}
}
}

View File

@ -40,7 +40,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
* TODO: we should use getStyleClass().add() rather than setStyle but it didn't
* seem to work properly
*/
class GroupTreeCell extends TreeCell<TreeNode> {
class GroupTreeCell extends TreeCell<GroupTreeNode> {
/**
* icon to use if this cell's TreeNode doesn't represent a group but just a
@ -71,11 +71,9 @@ class GroupTreeCell extends TreeCell<TreeNode> {
setStyle(style);
});
};
private final ReadOnlyObjectProperty<GroupComparators<?>> sortOrder;
private final ReadOnlyObjectProperty<TreeNodeComparator<?>> sortOrder;
GroupTreeCell(ReadOnlyObjectProperty<TreeNodeComparator<?>> sortOrderProperty) {
GroupTreeCell(ReadOnlyObjectProperty<GroupComparators<?>> sortOrderProperty) {
this.sortOrder = sortOrderProperty;
getStylesheets().add(GroupTreeCell.class.getResource("GroupTreeCell.css").toExternalForm());
getStyleClass().add("groupTreeCell"); //reduce indent to 5, default is 10 which uses up a lot of space.
@ -92,12 +90,12 @@ class GroupTreeCell extends TreeCell<TreeNode> {
* {@inheritDoc }
*/
@Override
protected synchronized void updateItem(final TreeNode treeNode, boolean empty) {
protected synchronized void updateItem(final GroupTreeNode treeNode, boolean empty) {
//if there was a previous group, remove the listeners
Optional.ofNullable(getItem())
.map(TreeNode::getGroup)
.map(GroupTreeNode::getGroup)
.ifPresent(group -> {
sortOrder.removeListener(fileCountListener);
sortOrder.addListener(fileCountListener);
group.fileIds().removeListener(fileCountListener);
group.seenProperty().removeListener(seenListener);
group.uncatCountProperty().removeListener(fileCountListener);
@ -159,7 +157,7 @@ class GroupTreeCell extends TreeCell<TreeNode> {
@Nonnull
private String getSeenStyleClass() {
return Optional.ofNullable(getItem())
.map(TreeNode::getGroup)
.map(GroupTreeNode::getGroup)
.map(DrawableGroup::isSeen)
.map(seen -> seen ? "" : "-fx-font-weight:bold;")
.orElse(""); //if item is null or group is null
@ -173,13 +171,12 @@ class GroupTreeCell extends TreeCell<TreeNode> {
*/
@Nonnull
private String getCountsText() {
return Optional.ofNullable(getItem())
.filter(treeNode -> treeNode.getGroup() != null)
.map(item ->
sortOrder.get() == TreeNodeComparator.ALPHABETICAL
? " (" + item.getSize() + ")"
: " (" + sortOrder.get().getFormattedValueOfTreeNode(item) + ")")
.orElse(""); //if item is null or group is null
.map(GroupTreeNode::getGroup)
.map(group ->
" (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
? group.getSize()
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
).orElse(""); //if item is null or group is null
}
}

View File

@ -38,7 +38,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
* {@link GroupTreeCell}. Each GroupTreeItem has a TreeNode which has a path
* segment and may or may not have a group
*/
class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeItem> {
class GroupTreeItem extends TreeItem<GroupTreeNode> {
static final Executor treeInsertTread = Executors.newSingleThreadExecutor();
@ -64,12 +64,12 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
/**
* the comparator if any used to sort the children of this item
*/
private Comparator<TreeNode> comp;
private Comparator<DrawableGroup> comp;
GroupTreeItem(String t, DrawableGroup g, Comparator<TreeNode> comp, boolean expanded) {
super(new TreeNode(t, g));
this.comp = comp;
GroupTreeItem(String t, DrawableGroup g, boolean expanded) {
super(new GroupTreeNode(t, g));
setExpanded(expanded);
comp = GroupComparators.ALPHABETICAL;
}
/**
@ -92,7 +92,7 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
* @param g Group to add
* @param tree True if it is part of a tree (versus a list)
*/
synchronized void insert(List<String> path, DrawableGroup g, Boolean tree) {
synchronized void insert(List<String> path, DrawableGroup g, boolean tree) {
if (tree) {
// Are we at the end of the recursion?
if (path.isEmpty()) {
@ -101,7 +101,7 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
String prefix = path.get(0);
GroupTreeItem prefixTreeItem = childMap.computeIfAbsent(prefix, (String t) -> {
final GroupTreeItem newTreeItem = new GroupTreeItem(t, null, comp, false);
final GroupTreeItem newTreeItem = new GroupTreeItem(t, null, false);
Platform.runLater(() -> {
getChildren().add(newTreeItem);
@ -111,7 +111,7 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
// recursively go into the path
treeInsertTread.execute(() -> {
prefixTreeItem.insert(path.subList(1, path.size()), g, tree);
prefixTreeItem.insert(path.subList(1, path.size()), g, true);
});
}
@ -119,25 +119,16 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
String join = StringUtils.join(path, "/");
//flat list
childMap.computeIfAbsent(join, (String t) -> {
final GroupTreeItem newTreeItem = new GroupTreeItem(t, g, comp, false);
newTreeItem.setExpanded(true);
final GroupTreeItem newTreeItem = new GroupTreeItem(t, g, true);
Platform.runLater(() -> {
getChildren().add(newTreeItem);
if (comp != null) {
getChildren().sort(Comparator.comparing(TreeItem::getValue, comp));
}
getChildren().sort(Comparator.comparing(treeItem -> treeItem.getValue().getGroup(), comp));
});
return newTreeItem;
});
}
}
@Override
public int compareTo(GroupTreeItem o) {
return comp.compare(this.getValue(), o.getValue());
}
synchronized GroupTreeItem getTreeItemForPath(List<String> path) {
if (path.isEmpty()) {
@ -178,9 +169,9 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
* @param newComp
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
synchronized void resortChildren(Comparator<TreeNode> newComp) {
synchronized void resortChildren(Comparator<DrawableGroup> newComp) {
this.comp = newComp;
getChildren().sort(Comparator.comparing(TreeItem::getValue, comp));
getChildren().sort(Comparator.comparing(treeItem -> treeItem.getValue().getGroup(), comp));
for (GroupTreeItem ti : childMap.values()) {
ti.resortChildren(comp);
}

View File

@ -23,7 +23,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
/**
*
*/
class TreeNode {
class GroupTreeNode {
private final String path;
private DrawableGroup group;
@ -36,7 +36,7 @@ class TreeNode {
return group;
}
TreeNode(String path, DrawableGroup group) {
GroupTreeNode(String path, DrawableGroup group) {
this.path = path;
this.group = group;
}
@ -44,24 +44,4 @@ class TreeNode {
void setGroup(DrawableGroup g) {
group = g;
}
public String getGroupByValueDislpayName() {
return (group == null) ? "" : group.getGroupByValueDislpayName();
}
public synchronized int getSize() {
return (group == null) ? 0 : group.getSize();
}
public double getHashHitDensity() {
return (group == null) ? 0 : group.getHashHitDensity();
}
public synchronized long getHashSetHitsCount() {
return (group == null) ? 0 : group.getHashSetHitsCount();
}
public final synchronized long getUncategorizedCount() {
return (group == null) ? 0 : group.getUncategorizedCount();
}
}

View File

@ -0,0 +1,96 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2016 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.gui.navpanel;
import java.util.function.Function;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionModel;
import javafx.scene.image.ImageView;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
/**
* Shows only groups with hash hits in a flat list, with controls to adjust
* sorting of list.
*/
final public class HashHitGroupList extends NavPanel<DrawableGroup> {
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private final ListView<DrawableGroup> groupList = new ListView<>();
/**
* sorted list of groups, setting a new comparator on this changes the
* sorting in the ListView.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private SortedList<DrawableGroup> sorted;
public HashHitGroupList(ImageGalleryController controller) {
super(controller);
FXMLConstructor.construct(this, "NavPanel.fxml");
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@Override
SelectionModel<DrawableGroup> getSelectionModel() {
return groupList.getSelectionModel();
}
@Override
Function< DrawableGroup, DrawableGroup> getDataItemMapper() {
return Function.identity(); // this view already works with groups, so do nothing
}
@Override
void applyGroupComparator() {
sorted.setComparator(getComparator());
}
@FXML
@Override
void initialize() {
super.initialize();
setText("Only Hash Hits");
setGraphic(new ImageView("org/sleuthkit/autopsy/imagegallery/images/hashset_hits.png"));
getBorderPane().setCenter(groupList);
sorted = getController().getGroupManager().getAnalyzedGroups().filtered((DrawableGroup t) -> t.getHashSetHitsCount() > 0).sorted();
groupList.setCellFactory(treeView -> new GroupListCell(getSortByBox().getSelectionModel().selectedItemProperty()));
groupList.setItems(sorted);
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@Override
void setFocusedGroup(DrawableGroup grouping) {
groupList.getSelectionModel().select(grouping);
}
@Override
GroupComparators<Long> getDefaultComparator() {
return GroupComparators.HIT_COUNT;
}
}

View File

@ -4,96 +4,61 @@
<?import javafx.collections.FXCollections?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.control.TreeView?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<fx:root maxHeight="1.7976931348623157E308" type="VBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<children>
<TabPane fx:id="navTabPane" maxHeight="1.7976931348623157E308" prefHeight="-1.0" prefWidth="-1.0" tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS">
<tabs>
<Tab fx:id="navTab" text="All Groups">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../images/folder-tree.png" />
</image>
</ImageView>
</graphic>
<content>
<AnchorPane fx:id="navAnchor">
<children>
<TreeView fx:id="navTree" maxHeight="1.7976931348623157E308" minWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0" style="" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</content>
</Tab>
<Tab fx:id="hashTab" text="Groups With Hash Hits">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../images/hashset_hits.png" />
</image>
</ImageView>
</graphic>
<content>
<BorderPane fx:id="treeBorder">
<center>
<AnchorPane fx:id="hashAnchor" />
</center>
<top>
<ToolBar BorderPane.alignment="CENTER">
<items>
<fx:root type="Tab" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" closable="false" >
<content>
<BorderPane prefHeight="200.0" prefWidth="200.0" fx:id="borderPane">
<top>
<ToolBar fx:id="toolBar" BorderPane.alignment="CENTER">
<items>
<Label text="Sort By:" />
<ComboBox fx:id="sortByBox" maxWidth="-Infinity" minWidth="-Infinity">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:value="Item 1" />
<String fx:value="Item 2" />
<String fx:value="Item 3" />
</FXCollections>
</items>
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:value="Item 1" />
<String fx:value="Item 2" />
<String fx:value="Item 3" />
</FXCollections>
</items>
</ComboBox>
<VBox alignment="CENTER_LEFT" prefHeight="-1.0" prefWidth="-1.0" spacing="2.0">
<children>
<RadioButton fx:id="ascRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="true" text="Ascending">
<graphic>
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<VBox alignment="CENTER_LEFT" prefHeight="-1.0" prefWidth="-1.0" spacing="2.0">
<children>
<RadioButton fx:id="ascRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="true" text="Ascending">
<graphic>
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../images/arrow_up.png" />
</image>
</ImageView>
</graphic>
<toggleGroup>
<ToggleGroup fx:id="orderGroup" />
</toggleGroup>
</RadioButton>
<RadioButton fx:id="descRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="false" text="Descending" toggleGroup="$orderGroup">
<graphic>
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
</image>
</ImageView>
</graphic>
<toggleGroup>
<ToggleGroup fx:id="orderGroup" />
</toggleGroup>
</RadioButton>
<RadioButton fx:id="descRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="false" text="Descending" toggleGroup="$orderGroup">
<graphic>
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../images/arrow_down.png" />
</image>
</ImageView>
</graphic>
</RadioButton>
</children>
</VBox>
</items>
</ToolBar>
</top>
</BorderPane>
</content>
</Tab>
</tabs>
</TabPane>
</children>
</image>
</ImageView>
</graphic>
</RadioButton>
</children>
</VBox>
</items>
</ToolBar>
</top>
</BorderPane>
</content>
</fx:root>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-16 Basis Technology Corp.
* Copyright 2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -19,63 +19,37 @@
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
import com.google.common.eventbus.Subscribe;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import java.util.function.Function;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.apache.commons.lang3.StringUtils;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
/**
* Display two trees. one shows all folders (groups) and calls out folders with
* images. the user can select folders with images to see them in the main
* GroupPane The other shows folders with hash set hits.
* Base class for Tabs in the left hand Navigation/Context area.
*/
final public class NavPanel extends VBox {
abstract class NavPanel<X> extends Tab {
@FXML
private ComboBox<TreeNodeComparator<?>> sortByBox;
private BorderPane borderPane;
@FXML
private TabPane navTabPane;
private ToolBar toolBar;
@FXML
private TreeView<TreeNode> navTree;
@FXML
private AnchorPane hashAnchor;
@FXML
private AnchorPane navAnchor;
@FXML
private Tab hashTab;
@FXML
private Tab navTab;
private final GroupTreeItem navTreeRoot = new GroupTreeItem("", null, TreeNodeComparator.ALPHABETICAL, true);
private final GroupTreeItem hashTreeRoot = new GroupTreeItem("", null, TreeNodeComparator.HIT_COUNT, true);
private ComboBox<GroupComparators<?>> sortByBox;
@FXML
private RadioButton ascRadio;
@ -87,161 +61,155 @@ final public class NavPanel extends VBox {
private RadioButton descRadio;
private final ImageGalleryController controller;
private final GroupManager groupManager;
private final CategoryManager categoryManager;
private TreeNodeComparator<?> hashSortOrder = TreeNodeComparator.HIT_COUNT;
public NavPanel(ImageGalleryController controller) {
NavPanel(ImageGalleryController controller) {
this.controller = controller;
FXMLConstructor.construct(this, "NavPanel.fxml");
}
@Subscribe
public void handleCategoryChange(CategoryManager.CategoryChangeEvent event) {
resortHashTree();
this.groupManager = controller.getGroupManager();
this.categoryManager = controller.getCategoryManager();
}
@FXML
void initialize() {
assert hashTab != null : "fx:id=\"hashTab\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert navTab != null : "fx:id=\"navTab\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert navTabPane != null : "fx:id=\"navTabPane\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert navTree != null : "fx:id=\"navTree\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert borderPane != null : "fx:id=\"borderPane\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert toolBar != null : "fx:id=\"toolBar\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert ascRadio != null : "fx:id=\"ascRadio\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert orderGroup != null : "fx:id=\"orderGroup\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert descRadio != null : "fx:id=\"descRadio\" was not injected: check your FXML file 'NavPanel.fxml'.";
VBox.setVgrow(this, Priority.ALWAYS);
sortByBox.getItems().setAll(TreeNodeComparator.getValues());
sortByBox.getSelectionModel().select(TreeNodeComparator.ALPHABETICAL);
sortByBox.getItems().setAll(GroupComparators.getValues());
sortByBox.getSelectionModel().select(getDefaultComparator());
orderGroup.selectedToggleProperty().addListener(order -> sortGroups());
sortByBox.getSelectionModel().selectedItemProperty().addListener(observable -> {
if (sortByBox.getSelectionModel().getSelectedItem() == TreeNodeComparator.UNCATEGORIZED_COUNT) {
controller.getCategoryManager().registerListener(NavPanel.this);
sortGroups();
//only need to listen to changes in category if we are sorting by/ showing the uncategorized count
if (sortByBox.getSelectionModel().getSelectedItem() == GroupComparators.UNCATEGORIZED_COUNT) {
categoryManager.registerListener(NavPanel.this);
} else {
controller.getCategoryManager().unregisterListener(NavPanel.this);
categoryManager.unregisterListener(NavPanel.this);
}
resortHashTree();
});
orderGroup.selectedToggleProperty().addListener(observable -> resortHashTree());
navTree.setCellFactory(treeView -> new GroupTreeCell(sortByBox.getSelectionModel().selectedItemProperty()));
navTree.setShowRoot(false);
navTree.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
navTree.getSelectionModel().selectedItemProperty().addListener(o -> updateControllersGroup());
navTree.setRoot(navTreeRoot);
navTabPane.getSelectionModel().selectedItemProperty().addListener(observable -> {
Tab selectedTab = navTabPane.getSelectionModel().getSelectedItem();
if (selectedTab == navTab) {
hashSortOrder = sortByBox.getSelectionModel().getSelectedItem();
sortByBox.getSelectionModel().select(TreeNodeComparator.ALPHABETICAL);
navTree.setRoot(navTreeRoot);
hashAnchor.getChildren().clear();
navAnchor.getChildren().add(navTree);
} else if (selectedTab == hashTab) {
sortByBox.getSelectionModel().select(hashSortOrder);
navTree.setRoot(hashTreeRoot);
navAnchor.getChildren().clear();
hashAnchor.getChildren().add(navTree);
resortHashTree();
}
});
controller.getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
while (change.next()) {
change.getAddedSubList().stream().forEach(this::insertGroup);
change.getRemoved().stream().forEach(this::removeFromTree);
}
});
//keep selection in sync with controller
controller.viewState().addListener(observable -> {
Optional.ofNullable(controller.viewState().get())
.map(GroupViewState::getGroup)
.ifPresent(this::setFocusedGroup);
});
for (DrawableGroup g : controller.getGroupManager().getAnalyzedGroups()) {
insertGroup(g);
}
getSelectionModel().selectedItemProperty().addListener(o -> updateControllersGroup());
}
private void insertGroup(DrawableGroup g) {
navTreeRoot.insert(groupingToPath(g), g, true);
if (g.getHashSetHitsCount() > 0) {
hashTreeRoot.insert(groupingToPath(g), g, false);
}
/**
* @return the default comparator used by this "view" to sort groups
*/
abstract GroupComparators<?> getDefaultComparator();
@Subscribe
public void handleCategoryChange(CategoryManager.CategoryChangeEvent event) {
sortGroups();
}
private void updateControllersGroup() {
Optional.ofNullable(navTree.getSelectionModel().getSelectedItem())
.map(TreeItem::getValue)
.map(TreeNode::getGroup)
/**
* @return the a comparator that will enforce the currently selected sorting
* options.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
Comparator<DrawableGroup> getComparator() {
Comparator<DrawableGroup> comparator = sortByBox.getSelectionModel().getSelectedItem();
return (orderGroup.getSelectedToggle() == ascRadio)
? comparator
: comparator.reversed();
}
/**
* notify controller about group selection in this view
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void updateControllersGroup() {
Optional.ofNullable(getSelectionModel().getSelectedItem())
.map(getDataItemMapper())
.ifPresent(group -> controller.advance(GroupViewState.tile(group), false));
}
@ThreadConfined(type = ThreadType.JFX)
private void resortHashTree() {
Comparator<TreeNode> treeNodeComparator = sortByBox.getSelectionModel().getSelectedItem();
if (orderGroup.getSelectedToggle() == descRadio) {
treeNodeComparator = treeNodeComparator.reversed();
}
TreeItem<TreeNode> selectedItem = navTree.getSelectionModel().getSelectedItem();
hashTreeRoot.resortChildren(treeNodeComparator);
navTree.getSelectionModel().select(selectedItem);
/**
* Sort the groups in this view according to the currently selected sorting
* options. Attempts to maintain selection.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void sortGroups() {
X selectedItem = getSelectionModel().getSelectedItem();
applyGroupComparator();
Optional.ofNullable(selectedItem)
.map(getDataItemMapper())
.ifPresent(this::setFocusedGroup);
}
/**
* Set the tree to the passed in group
*
* @param grouping
* @return a function that maps the "native" data type of this view to a
* DrawableGroup
*/
@ThreadConfined(type = ThreadType.JFX)
private void setFocusedGroup(DrawableGroup grouping) {
final GroupTreeItem treeItemForGroup = ((GroupTreeItem) navTree.getRoot()).getTreeItemForPath(groupingToPath(grouping));
abstract Function<X, DrawableGroup> getDataItemMapper();
if (treeItemForGroup != null) {
navTree.getSelectionModel().select(treeItemForGroup);
Platform.runLater(() -> {
int row = navTree.getRow(treeItemForGroup);
if (row != -1) {
navTree.scrollTo(row - 2); //put newly selected row 3 from the top
}
});
}
}
private List<String> groupingToPath(DrawableGroup g) {
String path = g.groupKey.getValueDisplayName();
if (g.groupKey.getAttribute() != DrawableAttribute.PATH
|| navTabPane.getSelectionModel().getSelectedItem() == hashTab) {
String stripStart = StringUtils.strip(path, "/");
return Arrays.asList(stripStart);
} else {
String[] cleanPathTokens = StringUtils.stripStart(path, "/").split("/");
return Arrays.asList(cleanPathTokens);
}
}
/**
* Apply the currently selected sorting options.
*/
abstract void applyGroupComparator();
/**
*
* @param g the value of g
* @param treeRoot the value of treeRoot
* @return get the selection model
*/
private void removeFromTree(DrawableGroup g) {
GroupTreeItem treeItemForGroup = navTreeRoot.getTreeItemForGroup(g);
if (treeItemForGroup != null) {
treeItemForGroup.removeFromParent();
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
abstract SelectionModel<X> getSelectionModel();
treeItemForGroup = hashTreeRoot.getTreeItemForGroup(g);
if (treeItemForGroup != null) {
treeItemForGroup.removeFromParent();
}
/**
* attempt to set the given group as the selected/focused group in this
* view.
*
* @param grouping the grouping to attempt to select
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
abstract void setFocusedGroup(DrawableGroup grouping);
////boring getters
BorderPane getBorderPane() {
return borderPane;
}
public void showTree() {
Platform.runLater(() -> {
navTabPane.getSelectionModel().select(navTab);
});
ToolBar getToolBar() {
return toolBar;
}
ComboBox<GroupComparators<?>> getSortByBox() {
return sortByBox;
}
RadioButton getAscRadio() {
return ascRadio;
}
ToggleGroup getOrderGroup() {
return orderGroup;
}
RadioButton getDescRadio() {
return descRadio;
}
ImageGalleryController getController() {
return controller;
}
GroupManager getGroupManager() {
return groupManager;
}
CategoryManager getCategoryManager() {
return categoryManager;
}
}

View File

@ -1,82 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-16 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.gui.navpanel;
import com.google.common.collect.ImmutableList;
import java.util.Comparator;
import java.util.function.Function;
/**
*
*/
final class TreeNodeComparator<T extends Comparable<T>> implements Comparator<TreeNode> {
static final TreeNodeComparator<Long> UNCATEGORIZED_COUNT =
new TreeNodeComparator<>("Uncategorized Count", TreeNode::getUncategorizedCount, String::valueOf, false);
static final TreeNodeComparator<String> ALPHABETICAL =
new TreeNodeComparator<>("Group Name", TreeNode::getGroupByValueDislpayName, String::valueOf, false);
static final TreeNodeComparator<Long> HIT_COUNT =
new TreeNodeComparator<>("Hit Count", TreeNode::getHashSetHitsCount, String::valueOf, true);
static final TreeNodeComparator<Integer> FILE_COUNT =
new TreeNodeComparator<>("Group Size", TreeNode::getSize, String::valueOf, true);
static final TreeNodeComparator<Double> HIT_FILE_RATIO =
new TreeNodeComparator<>("Hit Density", (treeNode) -> treeNode.getHashHitDensity(), density -> String.format("%.2f", density), true);
private final static ImmutableList<TreeNodeComparator<?>> values = ImmutableList.of(UNCATEGORIZED_COUNT, ALPHABETICAL, HIT_COUNT, FILE_COUNT, HIT_FILE_RATIO);
public static ImmutableList<TreeNodeComparator<?>> getValues() {
return values;
}
private final Function<TreeNode, T> extractor;
private final Function<T, String> valueFormatter;
private final boolean orderReveresed;
private final String displayName;
private TreeNodeComparator(String displayName, Function<TreeNode, T> extractor, Function<T, String> formatter, boolean defaultOrderReversed) {
this.displayName = displayName;
this.extractor = extractor;
this.orderReveresed = defaultOrderReversed;
this.valueFormatter = formatter;
}
@Override
public int compare(TreeNode o1, TreeNode o2) {
int compareTo = extractor.apply(o1).compareTo(extractor.apply(o2));
return orderReveresed ? -compareTo : compareTo;
}
public String getDisplayName() {
return displayName;
}
@Override
public String toString() {
return displayName;
}
String getFormattedValueOfTreeNode(TreeNode group) {
return valueFormatter.apply(extractor.apply(group));
}
}