fix description generation and clustering

- rename getAggregatredEvents to getEventClusters
- make more members private
- improve logic to get subclusters
- introduce notion of RelativeDetail for navigating DescriptionLoDs
This commit is contained in:
jmillman 2015-09-17 14:06:29 -04:00
parent ce930506bb
commit bd1e9a58d8
6 changed files with 160 additions and 84 deletions

View File

@ -337,7 +337,7 @@ public final class FilteredEventsModel {
zoom = requestedTypeZoom.get(); zoom = requestedTypeZoom.get();
lod = requestedLOD.get(); lod = requestedLOD.get();
} }
return repo.getAggregatedEvents(new ZoomParams(range, zoom, filter, lod)); return repo.getEventClusters(new ZoomParams(range, zoom, filter, lod));
} }
/** /**
@ -347,8 +347,8 @@ public final class FilteredEventsModel {
* range and pass the requested filter, using the given aggregation * range and pass the requested filter, using the given aggregation
* to control the grouping of events * to control the grouping of events
*/ */
public List<EventCluster> getAggregatedEvents(ZoomParams params) { public List<EventCluster> getEventClusters(ZoomParams params) {
return repo.getAggregatedEvents(params); return repo.getEventClusters(params);
} }
synchronized public boolean handleContentTagAdded(ContentTagAddedEvent evt) { synchronized public boolean handleContentTagAdded(ContentTagAddedEvent evt) {

View File

@ -98,7 +98,7 @@ public class EventsRepository {
private final LoadingCache<Long, TimeLineEvent> idToEventCache; private final LoadingCache<Long, TimeLineEvent> idToEventCache;
private final LoadingCache<ZoomParams, Map<EventType, Long>> eventCountsCache; private final LoadingCache<ZoomParams, Map<EventType, Long>> eventCountsCache;
private final LoadingCache<ZoomParams, List<EventCluster>> aggregateEventsCache; private final LoadingCache<ZoomParams, List<EventCluster>> eventClusterCache;
private final ObservableMap<Long, String> datasourcesMap = FXCollections.observableHashMap(); private final ObservableMap<Long, String> datasourcesMap = FXCollections.observableHashMap();
private final ObservableMap<Long, String> hashSetMap = FXCollections.observableHashMap(); private final ObservableMap<Long, String> hashSetMap = FXCollections.observableHashMap();
@ -146,7 +146,7 @@ public class EventsRepository {
.maximumSize(1000L) .maximumSize(1000L)
.expireAfterAccess(10, TimeUnit.MINUTES) .expireAfterAccess(10, TimeUnit.MINUTES)
.build(CacheLoader.from(eventDB::countEventsByType)); .build(CacheLoader.from(eventDB::countEventsByType));
aggregateEventsCache = CacheBuilder.newBuilder() eventClusterCache = CacheBuilder.newBuilder()
.maximumSize(1000L) .maximumSize(1000L)
.expireAfterAccess(10, TimeUnit.MINUTES .expireAfterAccess(10, TimeUnit.MINUTES
).build(CacheLoader.from(eventDB::getClusteredEvents)); ).build(CacheLoader.from(eventDB::getClusteredEvents));
@ -206,8 +206,8 @@ public class EventsRepository {
} }
synchronized public List<EventCluster> getAggregatedEvents(ZoomParams params) { synchronized public List<EventCluster> getEventClusters(ZoomParams params) {
return aggregateEventsCache.getUnchecked(params); return eventClusterCache.getUnchecked(params);
} }
synchronized public Map<EventType, Long> countEvents(ZoomParams params) { synchronized public Map<EventType, Long> countEvents(ZoomParams params) {
@ -218,7 +218,7 @@ public class EventsRepository {
minCache.invalidateAll(); minCache.invalidateAll();
maxCache.invalidateAll(); maxCache.invalidateAll();
eventCountsCache.invalidateAll(); eventCountsCache.invalidateAll();
aggregateEventsCache.invalidateAll(); eventClusterCache.invalidateAll();
idToEventCache.invalidateAll(); idToEventCache.invalidateAll();
} }
@ -292,7 +292,7 @@ public class EventsRepository {
synchronized private void invalidateCaches(Set<Long> updatedEventIDs) { synchronized private void invalidateCaches(Set<Long> updatedEventIDs) {
eventCountsCache.invalidateAll(); eventCountsCache.invalidateAll();
aggregateEventsCache.invalidateAll(); eventClusterCache.invalidateAll();
idToEventCache.invalidateAll(updatedEventIDs); idToEventCache.invalidateAll(updatedEventIDs);
try { try {
tagNames.setAll(autoCase.getSleuthkitCase().getTagNamesInUse()); tagNames.setAll(autoCase.getSleuthkitCase().getTagNamesInUse());

View File

@ -1,14 +1,29 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * Autopsy Forensic Browser
* To change this template file, choose Tools | Templates *
* and open the template in the editor. * 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.timeline.ui.detailview; package org.sleuthkit.autopsy.timeline.ui.detailview;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static java.util.Objects.nonNull;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -61,6 +76,7 @@ import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.filters.RootFilter; import org.sleuthkit.autopsy.timeline.filters.RootFilter;
import org.sleuthkit.autopsy.timeline.filters.TypeFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase;
@ -89,11 +105,11 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
b.setVisible(show); b.setVisible(show);
b.setManaged(show); b.setManaged(show);
} }
Map<EventType, DropShadow> dropShadowMap = new HashMap<>(); private final Map<EventType, DropShadow> dropShadowMap = new HashMap<>();
final Color evtColor; final Color evtColor;
private final S parentNode; private final S parentNode;
DescriptionVisibility descrVis; private DescriptionVisibility descrVis;
/** /**
* Pane that contains AggregateEventNodes of any 'subevents' if they are * Pane that contains AggregateEventNodes of any 'subevents' if they are
@ -102,7 +118,11 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
* //TODO: move more of the control of subnodes/events here and out of * //TODO: move more of the control of subnodes/events here and out of
* EventDetail Chart * EventDetail Chart
*/ */
final Pane subNodePane = new Pane(); private final Pane subNodePane = new Pane();
Pane getSubNodePane() {
return subNodePane;
}
/** /**
* The ImageView used to show the icon for this node's event's type * The ImageView used to show the icon for this node's event's type
@ -121,16 +141,28 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
private final T eventBundle; private final T eventBundle;
private final EventDetailChart chart; private final EventDetailChart chart;
final SleuthkitCase sleuthkitCase; private final SleuthkitCase sleuthkitCase;
final FilteredEventsModel eventsModel;
final Button plusButton; SleuthkitCase getSleuthkitCase() {
final Button minusButton; return sleuthkitCase;
}
final SimpleObjectProperty<DescriptionLOD> descLOD = new SimpleObjectProperty<>(); FilteredEventsModel getEventsModel() {
return eventsModel;
}
private final FilteredEventsModel eventsModel;
private final Button plusButton;
private final Button minusButton;
private final SimpleObjectProperty<DescriptionLOD> descLOD = new SimpleObjectProperty<>();
final HBox header; final HBox header;
final Region spacer = new Region(); Region getSpacer() {
return spacer;
}
private final Region spacer = new Region();
private final CollapseClusterAction collapseClusterAction; private final CollapseClusterAction collapseClusterAction;
private final ExpandClusterAction expandClusterAction; private final ExpandClusterAction expandClusterAction;
@ -160,9 +192,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
configureLODButton(minusButton); configureLODButton(minusButton);
HBox.setHgrow(spacer, Priority.ALWAYS); HBox.setHgrow(spacer, Priority.ALWAYS);
header = new HBox(getDescrLabel(), getCountLabel(), hashIV, tagIV, /* header = new HBox(getDescrLabel(), getCountLabel(), hashIV, tagIV, minusButton, plusButton);
* spacer,
*/ minusButton, plusButton);
header.setMinWidth(USE_PREF_SIZE); header.setMinWidth(USE_PREF_SIZE);
header.setPadding(new Insets(2, 5, 2, 5)); header.setPadding(new Insets(2, 5, 2, 5));
@ -244,12 +274,17 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
show(plusButton, showControls); show(plusButton, showControls);
} }
/**
* make a new filter intersecting the global filter with description and
* type filters to restrict sub-clusters
*
*/
RootFilter getSubClusterFilter() { RootFilter getSubClusterFilter() {
RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf(); RootFilter subClusterFilter = eventsModel.filterProperty().get().copyOf();
//make a new filter intersecting the global filter with description and type filters to restrict sub-clusters subClusterFilter.getSubFilters().addAll(
combinedFilter.getSubFilters().addAll(new DescriptionFilter(getEventBundle().getDescriptionLOD(), getDescription()), new DescriptionFilter(getEventBundle().getDescriptionLOD(), getDescription()),
new TypeFilter(getEventType())); new TypeFilter(getEventType()));
return combinedFilter; return subClusterFilter;
} }
abstract Collection<T> makeBundlesFromClusters(List<EventCluster> eventClusters); abstract Collection<T> makeBundlesFromClusters(List<EventCluster> eventClusters);
@ -324,59 +359,84 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
} }
/** /**
* loads sub-clusters at the given Description LOD * loads sub-bundles at the given Description LOD, continues
* *
* @param newDescriptionLOD * @param requestedDescrLoD
* @param expand
*/ */
final synchronized void loadSubClusters(DescriptionLOD newDescriptionLOD) { private synchronized void loadSubBundles(DescriptionLOD.RelativeDetail relativeDetail) {
subNodePane.getChildren().clear(); subNodePane.getChildren().clear();
if (newDescriptionLOD == getEventBundle().getDescriptionLOD()) { if (descLOD.get().withRelativeDetail(relativeDetail) == getEventBundle().getDescriptionLOD()) {
descLOD.set(getEventBundle().getDescriptionLOD());
showSpans(true); showSpans(true);
getChart().setRequiresLayout(true); chart.setRequiresLayout(true);
getChart().requestChartLayout(); chart.requestChartLayout();
} else { } else {
showSpans(false); showSpans(false);
RootFilter combinedFilter = getSubClusterFilter();
//make a new end inclusive span (to 'filter' with) // make new ZoomParams to query with
final Interval span = new Interval(getEventBundle().getStartMillis(), getEventBundle().getEndMillis() + 1000); final RootFilter subClusterFilter = getSubClusterFilter();
/*
* We need to extend end time because for the query by one second,
* because it is treated as an open interval but we want to include
* events at exactly the time of the last event in this cluster
*/
final Interval subClusterSpan = new Interval(getEventBundle().getStartMillis(), getEventBundle().getEndMillis() + 1000);
final EventTypeZoomLevel eventTypeZoomLevel = eventsModel.eventTypeZoomProperty().get();
final ZoomParams zoomParams = new ZoomParams(subClusterSpan, eventTypeZoomLevel, subClusterFilter, getDescLOD());
//make a task to load the subnodes LoggedTask<List<S>> loggedTask;
LoggedTask<List<S>> loggedTask = new LoggedTask<List<S>>( loggedTask = new LoggedTask<List<S>>(
NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) { NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) {
private Collection<T> bundles;
private volatile DescriptionLOD loadedDescriptionLoD = getDescLOD().withRelativeDetail(relativeDetail);
private DescriptionLOD next = loadedDescriptionLoD;
@Override @Override
protected List<S> call() throws Exception { protected List<S> call() throws Exception {
//query for the sub-clusters do {
List<EventCluster> aggregatedEvents = eventsModel.getAggregatedEvents(new ZoomParams(span, loadedDescriptionLoD = next;
eventsModel.eventTypeZoomProperty().get(), if (loadedDescriptionLoD == getEventBundle().getDescriptionLOD()) {
combinedFilter, return Collections.emptyList();
newDescriptionLOD)); }
bundles = loadBundles();
next = loadedDescriptionLoD.withRelativeDetail(relativeDetail);
} while (bundles.size() == 1 && nonNull(next));
return makeBundlesFromClusters(aggregatedEvents).stream() // return list of AbstractDetailViewNodes representing sub-bundles
.map(aggEvent -> { return bundles.stream()
return getNodeForCluser(aggEvent); .map(AbstractDetailViewNode.this::getNodeForBundle)
}).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters .collect(Collectors.toList());
}
private Collection<T> loadBundles() {
return makeBundlesFromClusters(eventsModel.getEventClusters(zoomParams.withDescrLOD(loadedDescriptionLoD)));
} }
@Override @Override
protected void succeeded() { protected void succeeded() {
chart.setCursor(Cursor.WAIT);
try { try {
getChart().setCursor(Cursor.WAIT); List<S> subBundleNodes = get();
if (subBundleNodes.isEmpty()) {
showSpans(true);
} else {
showSpans(false);
}
descLOD.set(loadedDescriptionLoD);
//assign subNodes and request chart layout //assign subNodes and request chart layout
subNodePane.getChildren().setAll(get()); subNodePane.getChildren().setAll(subBundleNodes);
setDescriptionVisibility(descrVis); chart.setRequiresLayout(true);
getChart().setRequiresLayout(true); chart.requestChartLayout();
getChart().requestChartLayout();
getChart().setCursor(null);
} catch (InterruptedException | ExecutionException ex) { } catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, "Error loading subnodes", ex); LOGGER.log(Level.SEVERE, "Error loading subnodes", ex);
} }
chart.setCursor(null);
} }
}; };
//start task //start task
getChart().getController().monitorTask(loggedTask); chart.getController().monitorTask(loggedTask);
} }
} }
@ -390,7 +450,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
this.descrVis = descrVis; this.descrVis = descrVis;
final int size = getEventBundle().getEventIDs().size(); final int size = getEventBundle().getEventIDs().size();
switch (descrVis) { switch (this.descrVis) {
case COUNT_ONLY: case COUNT_ONLY:
descrLabel.setText(""); descrLabel.setText("");
countLabel.setText(String.valueOf(size)); countLabel.setText(String.valueOf(size));
@ -411,7 +471,7 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
} }
} }
abstract S getNodeForCluser(T cluster); abstract S getNodeForBundle(T bundle);
/** /**
* event handler used for mouse events on {@link AggregateEventNode}s * event handler used for mouse events on {@link AggregateEventNode}s
@ -432,10 +492,10 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
} else if (t.isShortcutDown()) { } else if (t.isShortcutDown()) {
chart.selectedNodes.removeAll(AbstractDetailViewNode.this); chart.selectedNodes.removeAll(AbstractDetailViewNode.this);
} else if (t.getClickCount() > 1) { } else if (t.getClickCount() > 1) {
final DescriptionLOD next = descLOD.get().next(); final DescriptionLOD next = descLOD.get().moreDetailed();
if (next != null) { if (next != null) {
loadSubClusters(next); loadSubBundles(DescriptionLOD.RelativeDetail.MORE);
descLOD.set(next);
} }
} else { } else {
chart.selectedNodes.setAll(AbstractDetailViewNode.this); chart.selectedNodes.setAll(AbstractDetailViewNode.this);
@ -461,14 +521,15 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
private class ExpandClusterAction extends Action { private class ExpandClusterAction extends Action {
public ExpandClusterAction() { ExpandClusterAction() {
super("Expand"); super("Expand");
setGraphic(new ImageView(PLUS)); setGraphic(new ImageView(PLUS));
setEventHandler((ActionEvent t) -> { setEventHandler((ActionEvent t) -> {
final DescriptionLOD next = descLOD.get().next(); final DescriptionLOD next = descLOD.get().moreDetailed();
if (next != null) { if (next != null) {
loadSubClusters(next); loadSubBundles(DescriptionLOD.RelativeDetail.MORE);
descLOD.set(next);
} }
}); });
disabledProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL)); disabledProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL));
@ -477,15 +538,14 @@ public abstract class AbstractDetailViewNode< T extends EventBundle, S extends A
private class CollapseClusterAction extends Action { private class CollapseClusterAction extends Action {
public CollapseClusterAction() { CollapseClusterAction() {
super("Collapse"); super("Collapse");
setGraphic(new ImageView(MINUS)); setGraphic(new ImageView(MINUS));
setEventHandler((ActionEvent t) -> { setEventHandler((ActionEvent t) -> {
final DescriptionLOD previous = descLOD.get().previous(); final DescriptionLOD previous = descLOD.get().lessDetailed();
if (previous != null) { if (previous != null) {
loadSubClusters(previous); loadSubBundles(DescriptionLOD.RelativeDetail.LESS);
descLOD.set(previous);
} }
}); });
disabledProperty().bind(descLOD.isEqualTo(getEventBundle().getDescriptionLOD())); disabledProperty().bind(descLOD.isEqualTo(getEventBundle().getDescriptionLOD()));

View File

@ -66,8 +66,8 @@ public class EventClusterNode extends AbstractDetailViewNode<EventCluster, Event
minWidthProperty().bind(spanRegion.widthProperty()); minWidthProperty().bind(spanRegion.widthProperty());
header.setPrefWidth(USE_COMPUTED_SIZE); header.setPrefWidth(USE_COMPUTED_SIZE);
final BorderPane borderPane = new BorderPane(subNodePane, header, null, null, null); final BorderPane borderPane = new BorderPane(getSubNodePane(), header, null, null, null);
BorderPane.setAlignment(subNodePane, Pos.TOP_LEFT); BorderPane.setAlignment(getSubNodePane(), Pos.TOP_LEFT);
borderPane.setPrefWidth(USE_COMPUTED_SIZE); borderPane.setPrefWidth(USE_COMPUTED_SIZE);
getChildren().addAll(spanRegion, borderPane); getChildren().addAll(spanRegion, borderPane);
@ -86,8 +86,8 @@ public class EventClusterNode extends AbstractDetailViewNode<EventCluster, Event
if (!getEventCluster().getEventIDsWithHashHits().isEmpty()) { if (!getEventCluster().getEventIDsWithHashHits().isEmpty()) {
hashSetCounts = new HashMap<>(); hashSetCounts = new HashMap<>();
try { try {
for (TimeLineEvent tle : eventsModel.getEventsById(getEventCluster().getEventIDsWithHashHits())) { for (TimeLineEvent tle : getEventsModel().getEventsById(getEventCluster().getEventIDsWithHashHits())) {
Set<String> hashSetNames = sleuthkitCase.getAbstractFileById(tle.getFileID()).getHashSetNames(); Set<String> hashSetNames = getSleuthkitCase().getAbstractFileById(tle.getFileID()).getHashSetNames();
for (String hashSetName : hashSetNames) { for (String hashSetName : hashSetNames) {
hashSetCounts.merge(hashSetName, 1L, Long::sum); hashSetCounts.merge(hashSetName, 1L, Long::sum);
} }
@ -99,7 +99,7 @@ public class EventClusterNode extends AbstractDetailViewNode<EventCluster, Event
Map<String, Long> tagCounts = new HashMap<>(); Map<String, Long> tagCounts = new HashMap<>();
if (!getEventCluster().getEventIDsWithTags().isEmpty()) { if (!getEventCluster().getEventIDsWithTags().isEmpty()) {
tagCounts.putAll(eventsModel.getTagCountsByTagName(getEventCluster().getEventIDsWithTags())); tagCounts.putAll(getEventsModel().getTagCountsByTagName(getEventCluster().getEventIDsWithTags()));
} }
@ -174,7 +174,7 @@ public class EventClusterNode extends AbstractDetailViewNode<EventCluster, Event
} }
@Override @Override
EventClusterNode getNodeForCluser(EventCluster cluster) { EventClusterNode getNodeForBundle(EventCluster cluster) {
return new EventClusterNode(cluster, this, getChart()); return new EventClusterNode(cluster, this, getChart());
} }

View File

@ -18,7 +18,6 @@ import javafx.scene.layout.Region;
import static javafx.scene.layout.Region.USE_PREF_SIZE; import static javafx.scene.layout.Region.USE_PREF_SIZE;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import org.sleuthkit.autopsy.coreutils.ColorUtilities; import org.sleuthkit.autopsy.coreutils.ColorUtilities;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster; import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
import static org.sleuthkit.autopsy.timeline.ui.detailview.AbstractDetailViewNode.show; import static org.sleuthkit.autopsy.timeline.ui.detailview.AbstractDetailViewNode.show;
@ -28,14 +27,12 @@ import static org.sleuthkit.autopsy.timeline.ui.detailview.AbstractDetailViewNod
*/ */
public class EventStripeNode extends AbstractDetailViewNode<EventStripe, EventStripeNode> { public class EventStripeNode extends AbstractDetailViewNode<EventStripe, EventStripeNode> {
private static final Logger LOGGER = Logger.getLogger(EventClusterNode.class.getName());
private final HBox rangesHBox = new HBox(); private final HBox rangesHBox = new HBox();
EventStripeNode(EventStripe eventStripe, EventStripeNode parentNode, EventDetailChart chart) { EventStripeNode(EventStripe eventStripe, EventStripeNode parentNode, EventDetailChart chart) {
super(chart, eventStripe, parentNode); super(chart, eventStripe, parentNode);
minWidthProperty().bind(rangesHBox.widthProperty()); minWidthProperty().bind(rangesHBox.widthProperty());
final VBox internalVBox = new VBox(header, subNodePane); final VBox internalVBox = new VBox(header, getSubNodePane());
internalVBox.setAlignment(Pos.CENTER_LEFT); internalVBox.setAlignment(Pos.CENTER_LEFT);
for (Range<Long> range : eventStripe.getRanges()) { for (Range<Long> range : eventStripe.getRanges()) {
@ -57,7 +54,7 @@ public class EventStripeNode extends AbstractDetailViewNode<EventStripe, EventSt
@Override @Override
void showDescriptionLoDControls(final boolean showControls) { void showDescriptionLoDControls(final boolean showControls) {
super.showDescriptionLoDControls(showControls); super.showDescriptionLoDControls(showControls);
show(spacer, showControls); show(getSpacer(), showControls);
} }
@Override @Override
@ -105,8 +102,7 @@ public class EventStripeNode extends AbstractDetailViewNode<EventStripe, EventSt
} }
@Override @Override
EventStripeNode getNodeForCluser(EventStripe cluster) { EventStripeNode getNodeForBundle(EventStripe cluster) {
return new EventStripeNode(cluster, this, getChart()); return new EventStripeNode(cluster, this, getChart());
} }
} }

View File

@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.timeline.zooming;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
/** /**
* * Enumeration of all description levels of detail.
*/ */
public enum DescriptionLOD { public enum DescriptionLOD {
@ -39,7 +39,7 @@ public enum DescriptionLOD {
this.displayName = displayName; this.displayName = displayName;
} }
public DescriptionLOD next() { public DescriptionLOD moreDetailed() {
try { try {
return values()[ordinal() + 1]; return values()[ordinal() + 1];
} catch (ArrayIndexOutOfBoundsException e) { } catch (ArrayIndexOutOfBoundsException e) {
@ -47,11 +47,31 @@ public enum DescriptionLOD {
} }
} }
public DescriptionLOD previous() { public DescriptionLOD lessDetailed() {
try { try {
return values()[ordinal() - 1]; return values()[ordinal() - 1];
} catch (ArrayIndexOutOfBoundsException e) { } catch (ArrayIndexOutOfBoundsException e) {
return null; return null;
} }
} }
public DescriptionLOD withRelativeDetail(RelativeDetail relativeDetail) {
switch (relativeDetail) {
case EQUAL:
return this;
case MORE:
return moreDetailed();
case LESS:
return lessDetailed();
default:
throw new IllegalArgumentException("Unknown RelativeDetail value " + relativeDetail);
}
}
public enum RelativeDetail {
EQUAL,
MORE,
LESS;
}
} }