mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-16 09:47:42 +00:00
Merge branch 'develop' of https://github.com/sleuthkit/autopsy into vm_detection
This commit is contained in:
commit
013a85919f
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user