mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-17 10:17:41 +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());
|
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
|
* @param abstractFile file to encapsulate
|
||||||
*/
|
*/
|
||||||
AbstractAbstractFileNode(T abstractFile) {
|
AbstractAbstractFileNode(T abstractFile) {
|
||||||
@ -228,6 +228,13 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.objectId");
|
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
|
* Fill map with AbstractFile properties
|
||||||
*
|
*
|
||||||
* @param map map with preserved ordering, where property names/values
|
* @param map map with preserved ordering, where property names/values are
|
||||||
* are put
|
* put
|
||||||
* @param content to extract properties from
|
* @param content to extract properties from
|
||||||
*/
|
*/
|
||||||
public static void fillPropertyMap(Map<String, Object> map, AbstractFile content) {
|
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.HASHSETS.toString(), getHashSetHitsForFile(content));
|
||||||
map.put(AbstractFilePropertyType.MD5HASH.toString(), content.getMd5Hash() == null ? "" : content.getMd5Hash());
|
map.put(AbstractFilePropertyType.MD5HASH.toString(), content.getMd5Hash() == null ? "" : content.getMd5Hash());
|
||||||
map.put(AbstractFilePropertyType.ObjectID.toString(), content.getId());
|
map.put(AbstractFilePropertyType.ObjectID.toString(), content.getId());
|
||||||
|
map.put(AbstractFilePropertyType.MIMETYPE.toString(), content.getMIMEType() == null ? "" : content.getMIMEType());
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getContentDisplayName(AbstractFile file) {
|
static String getContentDisplayName(AbstractFile file) {
|
||||||
|
@ -18,6 +18,7 @@ AbstractAbstractFileNode.typeMetaColLbl=Type(Meta)
|
|||||||
AbstractAbstractFileNode.knownColLbl=Known
|
AbstractAbstractFileNode.knownColLbl=Known
|
||||||
AbstractAbstractFileNode.inHashsetsColLbl=In Hashsets
|
AbstractAbstractFileNode.inHashsetsColLbl=In Hashsets
|
||||||
AbstractAbstractFileNode.md5HashColLbl=MD5 Hash
|
AbstractAbstractFileNode.md5HashColLbl=MD5 Hash
|
||||||
|
AbstractAbstractFileNode.mimeType = MIME Type
|
||||||
AbstractContentChildren.CreateTSKNodeVisitor.exception.noNodeMsg=No Node defined for the given SleuthkitItem
|
AbstractContentChildren.CreateTSKNodeVisitor.exception.noNodeMsg=No Node defined for the given SleuthkitItem
|
||||||
AbstractContentChildren.createAutopsyNodeVisitor.exception.noNodeMsg=No Node defined for the given DisplayableItem
|
AbstractContentChildren.createAutopsyNodeVisitor.exception.noNodeMsg=No Node defined for the given DisplayableItem
|
||||||
AbstractContentNode.exception.cannotChangeSysName.msg=Cannot change the system name.
|
AbstractContentNode.exception.cannotChangeSysName.msg=Cannot change the system name.
|
||||||
|
@ -23,9 +23,9 @@ import java.net.MalformedURLException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.Node;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class supports programmer productivity by abstracting frequently used
|
* This class supports programmer productivity by abstracting frequently used
|
||||||
* code to load FXML-defined GUI components,
|
* code to load FXML-defined GUI components,
|
||||||
@ -38,7 +38,7 @@ public class FXMLConstructor {
|
|||||||
|
|
||||||
private static Logger logger = Logger.getLogger(FXMLConstructor.class.getName());
|
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;
|
final String name = "nbres:/" + StringUtils.replace(n.getClass().getPackage().getName(), ".", "/") + "/" + fxmlFileName;
|
||||||
|
|
||||||
try {
|
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.datamodel.grouping.GroupViewState;
|
||||||
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
|
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
|
||||||
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
|
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
|
||||||
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.NavPanel;
|
|
||||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
@ -94,6 +93,7 @@ import org.sleuthkit.datamodel.TskData;
|
|||||||
public final class ImageGalleryController implements Executor {
|
public final class ImageGalleryController implements Executor {
|
||||||
|
|
||||||
private final Executor execDelegate = Executors.newSingleThreadExecutor();
|
private final Executor execDelegate = Executors.newSingleThreadExecutor();
|
||||||
|
private Runnable showTree;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(Runnable command) {
|
public void execute(Runnable command) {
|
||||||
@ -153,7 +153,7 @@ public final class ImageGalleryController implements Executor {
|
|||||||
|
|
||||||
private Node infoOverlay;
|
private Node infoOverlay;
|
||||||
private SleuthkitCase sleuthKitCase;
|
private SleuthkitCase sleuthKitCase;
|
||||||
private NavPanel navPanel;
|
// private NavPanel navPanel;
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty getMetaDataCollapsed() {
|
public ReadOnlyBooleanProperty getMetaDataCollapsed() {
|
||||||
return metaDataCollapsed.getReadOnlyProperty();
|
return metaDataCollapsed.getReadOnlyProperty();
|
||||||
@ -266,8 +266,8 @@ public final class ImageGalleryController implements Executor {
|
|||||||
|
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
||||||
public void advance(GroupViewState newState, boolean forceShowTree) {
|
public void advance(GroupViewState newState, boolean forceShowTree) {
|
||||||
if (Objects.nonNull(navPanel) && forceShowTree) {
|
if (forceShowTree && showTree != null) {
|
||||||
navPanel.showTree();
|
showTree.run();
|
||||||
}
|
}
|
||||||
historyManager.advance(newState);
|
historyManager.advance(newState);
|
||||||
}
|
}
|
||||||
@ -482,8 +482,8 @@ public final class ImageGalleryController implements Executor {
|
|||||||
return tagsManager;
|
return tagsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNavPanel(NavPanel navPanel) {
|
public void setShowTree(Runnable showTree) {
|
||||||
this.navPanel = navPanel;
|
this.showTree = showTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UndoRedoManager getUndoManager() {
|
public UndoRedoManager getUndoManager() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-16 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* 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.embed.swing.JFXPanel;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.SplitPane;
|
import javafx.scene.control.SplitPane;
|
||||||
|
import javafx.scene.control.TabPane;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.netbeans.api.settings.ConvertAsProperties;
|
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.Toolbar;
|
||||||
import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.GroupPane;
|
import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.GroupPane;
|
||||||
import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.MetaDataPane;
|
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.
|
* Top component which displays ImageGallery interface.
|
||||||
@ -92,7 +95,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void closeTopComponent() {
|
public static void closeTopComponent() {
|
||||||
if(topComponentInitialized){
|
if (topComponentInitialized) {
|
||||||
final TopComponent etc = WindowManager.getDefault().findTopComponent("ImageGalleryTopComponent");
|
final TopComponent etc = WindowManager.getDefault().findTopComponent("ImageGalleryTopComponent");
|
||||||
if (etc != null) {
|
if (etc != null) {
|
||||||
try {
|
try {
|
||||||
@ -122,7 +125,8 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
|||||||
|
|
||||||
private GroupPane groupPane;
|
private GroupPane groupPane;
|
||||||
|
|
||||||
private NavPanel navPanel;
|
private GroupTree groupTree;
|
||||||
|
private HashHitGroupList hashHitList;
|
||||||
|
|
||||||
private VBox leftPane;
|
private VBox leftPane;
|
||||||
|
|
||||||
@ -149,8 +153,13 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
|||||||
|
|
||||||
metaDataTable = new MetaDataPane(controller);
|
metaDataTable = new MetaDataPane(controller);
|
||||||
|
|
||||||
navPanel = new NavPanel(controller);
|
groupTree = new GroupTree(controller);
|
||||||
leftPane = new VBox(navPanel, new SummaryTablePane(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(leftPane, Boolean.FALSE);
|
||||||
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
|
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
|
||||||
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
|
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
|
||||||
@ -158,7 +167,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
|||||||
splitPane.setDividerPositions(0.0, 1.0);
|
splitPane.setDividerPositions(0.0, 1.0);
|
||||||
|
|
||||||
ImageGalleryController.getDefault().setStacks(fullUIStack, centralStack);
|
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.application.Platform;
|
||||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import static javafx.concurrent.Worker.State.CANCELLED;
|
import static javafx.concurrent.Worker.State.CANCELLED;
|
||||||
@ -116,10 +118,13 @@ public class GroupManager {
|
|||||||
* --- current grouping/sorting attributes ---
|
* --- current grouping/sorting attributes ---
|
||||||
*/
|
*/
|
||||||
private volatile GroupSortBy sortBy = GroupSortBy.NONE;
|
private volatile GroupSortBy sortBy = GroupSortBy.NONE;
|
||||||
|
|
||||||
private volatile DrawableAttribute<?> groupBy = DrawableAttribute.PATH;
|
private volatile DrawableAttribute<?> groupBy = DrawableAttribute.PATH;
|
||||||
|
|
||||||
private volatile SortOrder sortOrder = SortOrder.ASCENDING;
|
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();
|
private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper();
|
||||||
|
|
||||||
public void setDB(DrawableDB db) {
|
public void setDB(DrawableDB db) {
|
||||||
@ -424,24 +429,39 @@ public class GroupManager {
|
|||||||
return sortBy;
|
return sortBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSortBy(GroupSortBy sortBy) {
|
void setSortBy(GroupSortBy sortBy) {
|
||||||
this.sortBy = sortBy;
|
this.sortBy = sortBy;
|
||||||
|
Platform.runLater(() -> sortByProp.set(sortBy));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<GroupSortBy> getSortByProperty() {
|
||||||
|
return sortByProp.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrawableAttribute<?> getGroupBy() {
|
public DrawableAttribute<?> getGroupBy() {
|
||||||
return groupBy;
|
return groupBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGroupBy(DrawableAttribute<?> groupBy) {
|
void setGroupBy(DrawableAttribute<?> groupBy) {
|
||||||
this.groupBy = groupBy;
|
this.groupBy = groupBy;
|
||||||
|
Platform.runLater(() -> groupByProp.set(groupBy));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<DrawableAttribute<?>> getGroupByProperty() {
|
||||||
|
return groupByProp.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SortOrder getSortOrder() {
|
public SortOrder getSortOrder() {
|
||||||
return sortOrder;
|
return sortOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSortOrder(SortOrder sortOrder) {
|
void setSortOrder(SortOrder sortOrder) {
|
||||||
this.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
|
* TODO: we should use getStyleClass().add() rather than setStyle but it didn't
|
||||||
* seem to work properly
|
* 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
|
* 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);
|
setStyle(style);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
private final ReadOnlyObjectProperty<GroupComparators<?>> sortOrder;
|
||||||
|
|
||||||
private final ReadOnlyObjectProperty<TreeNodeComparator<?>> sortOrder;
|
GroupTreeCell(ReadOnlyObjectProperty<GroupComparators<?>> sortOrderProperty) {
|
||||||
|
|
||||||
GroupTreeCell(ReadOnlyObjectProperty<TreeNodeComparator<?>> sortOrderProperty) {
|
|
||||||
|
|
||||||
this.sortOrder = sortOrderProperty;
|
this.sortOrder = sortOrderProperty;
|
||||||
getStylesheets().add(GroupTreeCell.class.getResource("GroupTreeCell.css").toExternalForm());
|
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.
|
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 }
|
* {@inheritDoc }
|
||||||
*/
|
*/
|
||||||
@Override
|
@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
|
//if there was a previous group, remove the listeners
|
||||||
Optional.ofNullable(getItem())
|
Optional.ofNullable(getItem())
|
||||||
.map(TreeNode::getGroup)
|
.map(GroupTreeNode::getGroup)
|
||||||
.ifPresent(group -> {
|
.ifPresent(group -> {
|
||||||
sortOrder.removeListener(fileCountListener);
|
sortOrder.addListener(fileCountListener);
|
||||||
group.fileIds().removeListener(fileCountListener);
|
group.fileIds().removeListener(fileCountListener);
|
||||||
group.seenProperty().removeListener(seenListener);
|
group.seenProperty().removeListener(seenListener);
|
||||||
group.uncatCountProperty().removeListener(fileCountListener);
|
group.uncatCountProperty().removeListener(fileCountListener);
|
||||||
@ -159,7 +157,7 @@ class GroupTreeCell extends TreeCell<TreeNode> {
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
private String getSeenStyleClass() {
|
private String getSeenStyleClass() {
|
||||||
return Optional.ofNullable(getItem())
|
return Optional.ofNullable(getItem())
|
||||||
.map(TreeNode::getGroup)
|
.map(GroupTreeNode::getGroup)
|
||||||
.map(DrawableGroup::isSeen)
|
.map(DrawableGroup::isSeen)
|
||||||
.map(seen -> seen ? "" : "-fx-font-weight:bold;")
|
.map(seen -> seen ? "" : "-fx-font-weight:bold;")
|
||||||
.orElse(""); //if item is null or group is null
|
.orElse(""); //if item is null or group is null
|
||||||
@ -173,13 +171,12 @@ class GroupTreeCell extends TreeCell<TreeNode> {
|
|||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private String getCountsText() {
|
private String getCountsText() {
|
||||||
|
|
||||||
return Optional.ofNullable(getItem())
|
return Optional.ofNullable(getItem())
|
||||||
.filter(treeNode -> treeNode.getGroup() != null)
|
.map(GroupTreeNode::getGroup)
|
||||||
.map(item ->
|
.map(group ->
|
||||||
sortOrder.get() == TreeNodeComparator.ALPHABETICAL
|
" (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
|
||||||
? " (" + item.getSize() + ")"
|
? group.getSize()
|
||||||
: " (" + sortOrder.get().getFormattedValueOfTreeNode(item) + ")")
|
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
|
||||||
.orElse(""); //if item is null or group is null
|
).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
|
* {@link GroupTreeCell}. Each GroupTreeItem has a TreeNode which has a path
|
||||||
* segment and may or may not have a group
|
* 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();
|
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
|
* 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) {
|
GroupTreeItem(String t, DrawableGroup g, boolean expanded) {
|
||||||
super(new TreeNode(t, g));
|
super(new GroupTreeNode(t, g));
|
||||||
this.comp = comp;
|
|
||||||
setExpanded(expanded);
|
setExpanded(expanded);
|
||||||
|
comp = GroupComparators.ALPHABETICAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,7 +92,7 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
* @param g Group to add
|
* @param g Group to add
|
||||||
* @param tree True if it is part of a tree (versus a list)
|
* @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) {
|
if (tree) {
|
||||||
// Are we at the end of the recursion?
|
// Are we at the end of the recursion?
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
@ -101,7 +101,7 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
String prefix = path.get(0);
|
String prefix = path.get(0);
|
||||||
|
|
||||||
GroupTreeItem prefixTreeItem = childMap.computeIfAbsent(prefix, (String t) -> {
|
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(() -> {
|
Platform.runLater(() -> {
|
||||||
getChildren().add(newTreeItem);
|
getChildren().add(newTreeItem);
|
||||||
@ -111,7 +111,7 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
|
|
||||||
// recursively go into the path
|
// recursively go into the path
|
||||||
treeInsertTread.execute(() -> {
|
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, "/");
|
String join = StringUtils.join(path, "/");
|
||||||
//flat list
|
//flat list
|
||||||
childMap.computeIfAbsent(join, (String t) -> {
|
childMap.computeIfAbsent(join, (String t) -> {
|
||||||
final GroupTreeItem newTreeItem = new GroupTreeItem(t, g, comp, false);
|
final GroupTreeItem newTreeItem = new GroupTreeItem(t, g, true);
|
||||||
newTreeItem.setExpanded(true);
|
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
getChildren().add(newTreeItem);
|
getChildren().add(newTreeItem);
|
||||||
if (comp != null) {
|
getChildren().sort(Comparator.comparing(treeItem -> treeItem.getValue().getGroup(), comp));
|
||||||
getChildren().sort(Comparator.comparing(TreeItem::getValue, comp));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return newTreeItem;
|
return newTreeItem;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(GroupTreeItem o) {
|
|
||||||
return comp.compare(this.getValue(), o.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized GroupTreeItem getTreeItemForPath(List<String> path) {
|
synchronized GroupTreeItem getTreeItemForPath(List<String> path) {
|
||||||
|
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
@ -178,9 +169,9 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
* @param newComp
|
* @param newComp
|
||||||
*/
|
*/
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
synchronized void resortChildren(Comparator<TreeNode> newComp) {
|
synchronized void resortChildren(Comparator<DrawableGroup> newComp) {
|
||||||
this.comp = 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()) {
|
for (GroupTreeItem ti : childMap.values()) {
|
||||||
ti.resortChildren(comp);
|
ti.resortChildren(comp);
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class TreeNode {
|
class GroupTreeNode {
|
||||||
|
|
||||||
private final String path;
|
private final String path;
|
||||||
private DrawableGroup group;
|
private DrawableGroup group;
|
||||||
@ -36,7 +36,7 @@ class TreeNode {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode(String path, DrawableGroup group) {
|
GroupTreeNode(String path, DrawableGroup group) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.group = group;
|
this.group = group;
|
||||||
}
|
}
|
||||||
@ -44,24 +44,4 @@ class TreeNode {
|
|||||||
void setGroup(DrawableGroup g) {
|
void setGroup(DrawableGroup g) {
|
||||||
group = 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.collections.FXCollections?>
|
||||||
<?import javafx.scene.control.ComboBox?>
|
<?import javafx.scene.control.ComboBox?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.RadioButton?>
|
|
||||||
<?import javafx.scene.control.Tab?>
|
<?import javafx.scene.control.Tab?>
|
||||||
<?import javafx.scene.control.TabPane?>
|
<?import javafx.scene.control.RadioButton?>
|
||||||
<?import javafx.scene.control.ToggleGroup?>
|
<?import javafx.scene.control.ToggleGroup?>
|
||||||
<?import javafx.scene.control.ToolBar?>
|
<?import javafx.scene.control.ToolBar?>
|
||||||
<?import javafx.scene.control.TreeView?>
|
|
||||||
<?import javafx.scene.image.Image?>
|
<?import javafx.scene.image.Image?>
|
||||||
<?import javafx.scene.image.ImageView?>
|
<?import javafx.scene.image.ImageView?>
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<?import javafx.scene.layout.BorderPane?>
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?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>
|
<fx:root type="Tab" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" closable="false" >
|
||||||
<TabPane fx:id="navTabPane" maxHeight="1.7976931348623157E308" prefHeight="-1.0" prefWidth="-1.0" tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS">
|
<content>
|
||||||
<tabs>
|
<BorderPane prefHeight="200.0" prefWidth="200.0" fx:id="borderPane">
|
||||||
<Tab fx:id="navTab" text="All Groups">
|
<top>
|
||||||
<graphic>
|
<ToolBar fx:id="toolBar" BorderPane.alignment="CENTER">
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
<items>
|
||||||
<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>
|
|
||||||
<Label text="Sort By:" />
|
<Label text="Sort By:" />
|
||||||
<ComboBox fx:id="sortByBox" maxWidth="-Infinity" minWidth="-Infinity">
|
<ComboBox fx:id="sortByBox" maxWidth="-Infinity" minWidth="-Infinity">
|
||||||
<items>
|
<items>
|
||||||
<FXCollections fx:factory="observableArrayList">
|
<FXCollections fx:factory="observableArrayList">
|
||||||
<String fx:value="Item 1" />
|
<String fx:value="Item 1" />
|
||||||
<String fx:value="Item 2" />
|
<String fx:value="Item 2" />
|
||||||
<String fx:value="Item 3" />
|
<String fx:value="Item 3" />
|
||||||
</FXCollections>
|
</FXCollections>
|
||||||
</items>
|
</items>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<VBox alignment="CENTER_LEFT" prefHeight="-1.0" prefWidth="-1.0" spacing="2.0">
|
<VBox alignment="CENTER_LEFT" prefHeight="-1.0" prefWidth="-1.0" spacing="2.0">
|
||||||
<children>
|
<children>
|
||||||
<RadioButton fx:id="ascRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="true" text="Ascending">
|
<RadioButton fx:id="ascRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="true" text="Ascending">
|
||||||
<graphic>
|
<graphic>
|
||||||
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
<Image url="@../../images/arrow_up.png" />
|
<Image url="@../../images/arrow_up.png" />
|
||||||
</image>
|
</image>
|
||||||
</ImageView>
|
</ImageView>
|
||||||
</graphic>
|
</graphic>
|
||||||
<toggleGroup>
|
<toggleGroup>
|
||||||
<ToggleGroup fx:id="orderGroup" />
|
<ToggleGroup fx:id="orderGroup" />
|
||||||
</toggleGroup>
|
</toggleGroup>
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
<RadioButton fx:id="descRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="false" text="Descending" toggleGroup="$orderGroup">
|
<RadioButton fx:id="descRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="false" text="Descending" toggleGroup="$orderGroup">
|
||||||
<graphic>
|
<graphic>
|
||||||
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
<Image url="@../../images/arrow_down.png" />
|
<Image url="@../../images/arrow_down.png" />
|
||||||
</image>
|
</image>
|
||||||
</ImageView>
|
</ImageView>
|
||||||
</graphic>
|
</graphic>
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
</items>
|
</items>
|
||||||
</ToolBar>
|
</ToolBar>
|
||||||
</top>
|
</top>
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
</content>
|
</content>
|
||||||
</Tab>
|
|
||||||
</tabs>
|
|
||||||
</TabPane>
|
|
||||||
</children>
|
|
||||||
</fx:root>
|
</fx:root>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-16 Basis Technology Corp.
|
* Copyright 2016 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -19,63 +19,37 @@
|
|||||||
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
|
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javafx.application.Platform;
|
import java.util.function.Function;
|
||||||
import javafx.collections.ListChangeListener;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.RadioButton;
|
import javafx.scene.control.RadioButton;
|
||||||
import javafx.scene.control.SelectionMode;
|
import javafx.scene.control.SelectionModel;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
import javafx.scene.control.TabPane;
|
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
import javafx.scene.control.TreeItem;
|
import javafx.scene.control.ToolBar;
|
||||||
import javafx.scene.control.TreeView;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.layout.Priority;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
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.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
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.DrawableGroup;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display two trees. one shows all folders (groups) and calls out folders with
|
* Base class for Tabs in the left hand Navigation/Context area.
|
||||||
* images. the user can select folders with images to see them in the main
|
|
||||||
* GroupPane The other shows folders with hash set hits.
|
|
||||||
*/
|
*/
|
||||||
final public class NavPanel extends VBox {
|
abstract class NavPanel<X> extends Tab {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ComboBox<TreeNodeComparator<?>> sortByBox;
|
private BorderPane borderPane;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TabPane navTabPane;
|
private ToolBar toolBar;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TreeView<TreeNode> navTree;
|
private ComboBox<GroupComparators<?>> sortByBox;
|
||||||
|
|
||||||
@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);
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private RadioButton ascRadio;
|
private RadioButton ascRadio;
|
||||||
@ -87,161 +61,155 @@ final public class NavPanel extends VBox {
|
|||||||
private RadioButton descRadio;
|
private RadioButton descRadio;
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
|
private final GroupManager groupManager;
|
||||||
|
private final CategoryManager categoryManager;
|
||||||
|
|
||||||
private TreeNodeComparator<?> hashSortOrder = TreeNodeComparator.HIT_COUNT;
|
NavPanel(ImageGalleryController controller) {
|
||||||
|
|
||||||
public NavPanel(ImageGalleryController controller) {
|
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
FXMLConstructor.construct(this, "NavPanel.fxml");
|
this.groupManager = controller.getGroupManager();
|
||||||
}
|
this.categoryManager = controller.getCategoryManager();
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void handleCategoryChange(CategoryManager.CategoryChangeEvent event) {
|
|
||||||
resortHashTree();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert hashTab != null : "fx:id=\"hashTab\" 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 navTab != null : "fx:id=\"navTab\" 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 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 sortByBox != null : "fx:id=\"sortByBox\" 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(GroupComparators.getValues());
|
||||||
|
sortByBox.getSelectionModel().select(getDefaultComparator());
|
||||||
sortByBox.getItems().setAll(TreeNodeComparator.getValues());
|
orderGroup.selectedToggleProperty().addListener(order -> sortGroups());
|
||||||
sortByBox.getSelectionModel().select(TreeNodeComparator.ALPHABETICAL);
|
|
||||||
|
|
||||||
sortByBox.getSelectionModel().selectedItemProperty().addListener(observable -> {
|
sortByBox.getSelectionModel().selectedItemProperty().addListener(observable -> {
|
||||||
|
sortGroups();
|
||||||
if (sortByBox.getSelectionModel().getSelectedItem() == TreeNodeComparator.UNCATEGORIZED_COUNT) {
|
//only need to listen to changes in category if we are sorting by/ showing the uncategorized count
|
||||||
controller.getCategoryManager().registerListener(NavPanel.this);
|
if (sortByBox.getSelectionModel().getSelectedItem() == GroupComparators.UNCATEGORIZED_COUNT) {
|
||||||
|
categoryManager.registerListener(NavPanel.this);
|
||||||
} else {
|
} 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 -> {
|
controller.viewState().addListener(observable -> {
|
||||||
Optional.ofNullable(controller.viewState().get())
|
Optional.ofNullable(controller.viewState().get())
|
||||||
.map(GroupViewState::getGroup)
|
.map(GroupViewState::getGroup)
|
||||||
.ifPresent(this::setFocusedGroup);
|
.ifPresent(this::setFocusedGroup);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (DrawableGroup g : controller.getGroupManager().getAnalyzedGroups()) {
|
getSelectionModel().selectedItemProperty().addListener(o -> updateControllersGroup());
|
||||||
insertGroup(g);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertGroup(DrawableGroup g) {
|
/**
|
||||||
navTreeRoot.insert(groupingToPath(g), g, true);
|
* @return the default comparator used by this "view" to sort groups
|
||||||
if (g.getHashSetHitsCount() > 0) {
|
*/
|
||||||
hashTreeRoot.insert(groupingToPath(g), g, false);
|
abstract GroupComparators<?> getDefaultComparator();
|
||||||
}
|
|
||||||
|
@Subscribe
|
||||||
|
public void handleCategoryChange(CategoryManager.CategoryChangeEvent event) {
|
||||||
|
sortGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateControllersGroup() {
|
/**
|
||||||
Optional.ofNullable(navTree.getSelectionModel().getSelectedItem())
|
* @return the a comparator that will enforce the currently selected sorting
|
||||||
.map(TreeItem::getValue)
|
* options.
|
||||||
.map(TreeNode::getGroup)
|
*/
|
||||||
|
@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));
|
.ifPresent(group -> controller.advance(GroupViewState.tile(group), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadType.JFX)
|
/**
|
||||||
private void resortHashTree() {
|
* Sort the groups in this view according to the currently selected sorting
|
||||||
Comparator<TreeNode> treeNodeComparator = sortByBox.getSelectionModel().getSelectedItem();
|
* options. Attempts to maintain selection.
|
||||||
if (orderGroup.getSelectedToggle() == descRadio) {
|
*/
|
||||||
treeNodeComparator = treeNodeComparator.reversed();
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
}
|
void sortGroups() {
|
||||||
TreeItem<TreeNode> selectedItem = navTree.getSelectionModel().getSelectedItem();
|
X selectedItem = getSelectionModel().getSelectedItem();
|
||||||
hashTreeRoot.resortChildren(treeNodeComparator);
|
applyGroupComparator();
|
||||||
navTree.getSelectionModel().select(selectedItem);
|
Optional.ofNullable(selectedItem)
|
||||||
|
.map(getDataItemMapper())
|
||||||
|
.ifPresent(this::setFocusedGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the tree to the passed in group
|
* @return a function that maps the "native" data type of this view to a
|
||||||
*
|
* DrawableGroup
|
||||||
* @param grouping
|
|
||||||
*/
|
*/
|
||||||
@ThreadConfined(type = ThreadType.JFX)
|
abstract Function<X, DrawableGroup> getDataItemMapper();
|
||||||
private void setFocusedGroup(DrawableGroup grouping) {
|
|
||||||
final GroupTreeItem treeItemForGroup = ((GroupTreeItem) navTree.getRoot()).getTreeItemForPath(groupingToPath(grouping));
|
|
||||||
|
|
||||||
if (treeItemForGroup != null) {
|
/**
|
||||||
navTree.getSelectionModel().select(treeItemForGroup);
|
* Apply the currently selected sorting options.
|
||||||
Platform.runLater(() -> {
|
*/
|
||||||
int row = navTree.getRow(treeItemForGroup);
|
abstract void applyGroupComparator();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param g the value of g
|
* @return get the selection model
|
||||||
* @param treeRoot the value of treeRoot
|
|
||||||
*/
|
*/
|
||||||
private void removeFromTree(DrawableGroup g) {
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
GroupTreeItem treeItemForGroup = navTreeRoot.getTreeItemForGroup(g);
|
abstract SelectionModel<X> getSelectionModel();
|
||||||
if (treeItemForGroup != null) {
|
|
||||||
treeItemForGroup.removeFromParent();
|
|
||||||
}
|
|
||||||
|
|
||||||
treeItemForGroup = hashTreeRoot.getTreeItemForGroup(g);
|
/**
|
||||||
if (treeItemForGroup != null) {
|
* attempt to set the given group as the selected/focused group in this
|
||||||
treeItemForGroup.removeFromParent();
|
* 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() {
|
ToolBar getToolBar() {
|
||||||
Platform.runLater(() -> {
|
return toolBar;
|
||||||
navTabPane.getSelectionModel().select(navTab);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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