mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-17 10:17:41 +00:00
Merge remote-tracking branch 'upstream/TL-list-view' into develop
This commit is contained in:
commit
c398dc4536
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2015 Basis Technology Corp.
|
||||
*
|
||||
* Copyright 2011-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.
|
||||
@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.actions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
@ -26,8 +27,8 @@ import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
@ -38,7 +39,6 @@ public class AddBlackboardArtifactTagAction extends AddTagAction {
|
||||
// This class is a singleton to support multi-selection of nodes, since
|
||||
// org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every
|
||||
// node in the array returns a reference to the same action object from Node.getActions(boolean).
|
||||
|
||||
private static AddBlackboardArtifactTagAction instance;
|
||||
|
||||
public static synchronized AddBlackboardArtifactTagAction getInstance() {
|
||||
@ -63,7 +63,14 @@ public class AddBlackboardArtifactTagAction extends AddTagAction {
|
||||
|
||||
@Override
|
||||
protected void addTag(TagName tagName, String comment) {
|
||||
final Collection<? extends BlackboardArtifact> selectedArtifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class);
|
||||
/*
|
||||
* The documentation for Lookup.lookupAll() explicitly says that the
|
||||
* collection it returns may contain duplicates. Within this invocation
|
||||
* of addTag(), we don't want to tag the same BlackboardArtifact more
|
||||
* than once, so we dedupe the BlackboardArtifacts by stuffing them into
|
||||
* a HashSet.
|
||||
*/
|
||||
final Collection<BlackboardArtifact> selectedArtifacts = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class));
|
||||
|
||||
new Thread(() -> {
|
||||
for (BlackboardArtifact artifact : selectedArtifacts) {
|
||||
|
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2015 Basis Technology Corp.
|
||||
*
|
||||
* Copyright 2011-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.
|
||||
@ -19,13 +19,13 @@
|
||||
package org.sleuthkit.autopsy.actions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
@ -40,7 +40,6 @@ public class AddContentTagAction extends AddTagAction {
|
||||
// This class is a singleton to support multi-selection of nodes, since
|
||||
// org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every
|
||||
// node in the array returns a reference to the same action object from Node.getActions(boolean).
|
||||
|
||||
private static AddContentTagAction instance;
|
||||
|
||||
public static synchronized AddContentTagAction getInstance() {
|
||||
@ -63,7 +62,13 @@ public class AddContentTagAction extends AddTagAction {
|
||||
|
||||
@Override
|
||||
protected void addTag(TagName tagName, String comment) {
|
||||
final Collection<? extends AbstractFile> selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class);
|
||||
/*
|
||||
* The documentation for Lookup.lookupAll() explicitly says that the
|
||||
* collection it returns may contain duplicates. Within this invocation
|
||||
* of addTag(), we don't want to tag the same AbstractFile more than
|
||||
* once, so we dedupe the AbstractFiles by stuffing them into a HashSet.
|
||||
*/
|
||||
final Collection<AbstractFile> selectedFiles = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||
|
||||
new Thread(() -> {
|
||||
for (AbstractFile file : selectedFiles) {
|
||||
|
@ -9,7 +9,7 @@ Timeline.zoomOutButton.text=Zoom Out
|
||||
Timeline.goToButton.text=Go To\:
|
||||
Timeline.yearBarChart.x.years=Years
|
||||
Timeline.resultPanel.loading=Loading...
|
||||
Timeline.node.root=Root
|
||||
|
||||
TimelineFrame.title=Timeline
|
||||
TimelinePanel.jButton1.text=6m
|
||||
TimelinePanel.jButton13.text=all
|
||||
@ -24,4 +24,4 @@ TimelinePanel.jButton7.text=3d
|
||||
TimelinePanel.jButton2.text=1m
|
||||
TimelinePanel.jButton3.text=3m
|
||||
TimelinePanel.jButton4.text=2w
|
||||
ProgressWindow.progressHeader.text=\
|
||||
ProgressWindow.progressHeader.text=\
|
||||
|
@ -1,48 +1,48 @@
|
||||
CTL_MakeTimeline=\u300C\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u300D
|
||||
CTL_TimeLineTopComponent=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A6\u30A3\u30F3\u30C9\u30A6
|
||||
CTL_TimeLineTopComponentAction=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C8\u30C3\u30D7\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8
|
||||
HINT_TimeLineTopComponent=\u3053\u308C\u306F\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A6\u30A3\u30F3\u30C9\u30A6\u3067\u3059
|
||||
OpenTimelineAction.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3
|
||||
Timeline.frameName.text={0} - Autopsy\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3
|
||||
Timeline.goToButton.text=\u4E0B\u8A18\u3078\u79FB\u52D5\uFF1A
|
||||
Timeline.node.root=\u30EB\u30FC\u30C8
|
||||
Timeline.pushDescrLOD.confdlg.msg={0}\u30A4\u30D9\u30F3\u30C8\u306E\u8A73\u7D30\u304C\u8868\u793A\u53EF\u80FD\u3067\u3059\u3002\u3053\u306E\u51E6\u7406\u306F\u9577\u6642\u9593\u304B\u304B\u308B\u3082\u3057\u304F\u306FAutopsy\u3092\u30AF\u30E9\u30C3\u30B7\u30E5\u3059\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\n\n\u5B9F\u884C\u3057\u307E\u3059\u304B\uFF1F
|
||||
Timeline.resultPanel.loading=\u30ED\u30FC\u30C9\u4E2D\u30FB\u30FB\u30FB
|
||||
Timeline.resultsPanel.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u7D50\u679C
|
||||
Timeline.runJavaFxThread.progress.creating=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u4F5C\u6210\u4E2D\u30FB\u30FB\u30FB
|
||||
Timeline.zoomOutButton.text=\u30BA\u30FC\u30E0\u30A2\u30A6\u30C8
|
||||
TimelineFrame.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3
|
||||
TimeLineTopComponent.eventsTab.name=\u30A4\u30D9\u30F3\u30C8
|
||||
TimeLineTopComponent.filterTab.name=\u30D5\u30A3\u30EB\u30BF\u30FC
|
||||
OpenTimeLineAction.msgdlg.text=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u304C\u3042\u308A\u307E\u305B\u3093\u3002
|
||||
PrompDialogManager.buttonType.continueNoUpdate=\u66F4\u65B0\u305B\u305A\u6B21\u3078
|
||||
PrompDialogManager.buttonType.showTimeline=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u8868\u793A
|
||||
PrompDialogManager.buttonType.update=\u66F4\u65B0
|
||||
PromptDialogManager.confirmDuringIngest.contentText=\u6B21\u3078\u9032\u307F\u307E\u3059\u304B\uFF1F
|
||||
PromptDialogManager.confirmDuringIngest.headerText=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u304C\u5B8C\u4E86\u3059\u308B\u524D\u306B\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u8868\u793A\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u307E\u3059\u3002\n\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u304C\u5B8C\u6210\u3057\u3066\u3044\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002
|
||||
PromptDialogManager.progressDialog.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u3092\u5165\u529B\u4E2D
|
||||
PromptDialogManager.rebuildPrompt.details=\u8A73\u7D30\uFF1A
|
||||
PromptDialogManager.rebuildPrompt.headerText=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u304C\u4E0D\u5B8C\u5168\u307E\u305F\u306F\u6700\u65B0\u3067\u306F\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\n \u6B20\u843D\u3057\u3066\u3044\u308B\u307E\u305F\u306F\u4E0D\u6B63\u78BA\u306A\u30A4\u30D9\u30F3\u30C8\u304C\u4E00\u90E8\u3042\u308B\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\u4E00\u90E8\u306E\u6A5F\u80FD\u304C\u5229\u7528\u3067\u304D\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002
|
||||
Timeline.confirmation.dialogs.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u66F4\u65B0\u3057\u307E\u3059\u304B\uFF1F
|
||||
Timeline.pushDescrLOD.confdlg.title=\u8AAC\u660E\u306E\u8A18\u8FF0\u30EC\u30D9\u30EB\u3092\u5909\u66F4\u3057\u307E\u3059\u304B\uFF1F
|
||||
TimeLineController.errorTitle=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A8\u30E9\u30FC
|
||||
TimeLineController.outOfDate.errorMessage=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u304C\u6700\u65B0\u304B\u78BA\u8A8D\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u66F4\u65B0\u304C\u5FC5\u8981\u3060\u3068\u60F3\u5B9A\u3057\u307E\u3059\u3002
|
||||
TimeLineController.rebuildReasons.incompleteOldSchema=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A4\u30D9\u30F3\u30C8\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306B\u4E0D\u5B8C\u5168\u306A\u60C5\u5831\u304C\u4EE5\u524D\u5165\u529B\u3055\u308C\u3066\u3044\u307E\u3057\u305F\uFF1A\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u66F4\u65B0\u3057\u306A\u3044\u3068\u3001\u4E00\u90E8\u306E\u6A5F\u80FD\u304C\u5229\u7528\u3067\u304D\u306A\u3044\u3001\u307E\u305F\u306F\u6A5F\u80FD\u3057\u306A\u3044\u304B\u3082\u3057\u308C\u306A\u3044\u3067\u3059\u3002
|
||||
TimeLineController.rebuildReasons.ingestWasRunning=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u304C\u5B9F\u884C\u4E2D\u306B\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A4\u30D9\u30F3\u30C8\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306B\u60C5\u5831\u304C\u5165\u529B\u3055\u308C\u3066\u3044\u307E\u3057\u305F\uFF1A\u30A4\u30D9\u30F3\u30C8\u304C\u6B20\u3051\u3066\u3044\u308B\u3001\u4E0D\u5B8C\u5168\u3001\u307E\u305F\u306F\u4E0D\u6B63\u78BA\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002
|
||||
TimeLineController.rebuildReasons.outOfDate=\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u304C\u6700\u65B0\u3067\u306F\u3042\u308A\u307E\u305B\u3093\uFF1A\u898B\u308C\u306A\u3044\u30A4\u30D9\u30F3\u30C8\u304C\u3042\u308A\u307E\u3059\u3002
|
||||
TimeLineController.rebuildReasons.outOfDateError=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u304C\u6700\u65B0\u304B\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002
|
||||
TimeLinecontroller.updateNowQuestion=\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u4ECA\u66F4\u65B0\u3057\u307E\u3059\u304B\uFF1F
|
||||
CTL_MakeTimeline=\u300c\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u300d
|
||||
CTL_TimeLineTopComponent=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a6\u30a3\u30f3\u30c9\u30a6
|
||||
CTL_TimeLineTopComponentAction=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c8\u30c3\u30d7\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8
|
||||
HINT_TimeLineTopComponent=\u3053\u308c\u306f\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a6\u30a3\u30f3\u30c9\u30a6\u3067\u3059
|
||||
OpenTimelineAction.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3
|
||||
Timeline.frameName.text={0} - Autopsy\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3
|
||||
Timeline.goToButton.text=\u4e0b\u8a18\u3078\u79fb\u52d5\uff1a
|
||||
Timeline.pushDescrLOD.confdlg.msg={0}\u30a4\u30d9\u30f3\u30c8\u306e\u8a73\u7d30\u304c\u8868\u793a\u53ef\u80fd\u3067\u3059\u3002\u3053\u306e\u51e6\u7406\u306f\u9577\u6642\u9593\u304b\u304b\u308b\u3082\u3057\u304f\u306fAutopsy\u3092\u30af\u30e9\u30c3\u30b7\u30e5\u3059\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002\n\n\u5b9f\u884c\u3057\u307e\u3059\u304b\uff1f
|
||||
Timeline.resultPanel.loading=\u30ed\u30fc\u30c9\u4e2d\u30fb\u30fb\u30fb
|
||||
Timeline.resultsPanel.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u7d50\u679c
|
||||
Timeline.runJavaFxThread.progress.creating=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u4f5c\u6210\u4e2d\u30fb\u30fb\u30fb
|
||||
Timeline.zoomOutButton.text=\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8
|
||||
TimelineFrame.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3
|
||||
TimeLineTopComponent.eventsTab.name=\u30a4\u30d9\u30f3\u30c8
|
||||
TimeLineTopComponent.filterTab.name=\u30d5\u30a3\u30eb\u30bf\u30fc
|
||||
OpenTimeLineAction.msgdlg.text=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002
|
||||
PrompDialogManager.buttonType.continueNoUpdate=\u66f4\u65b0\u305b\u305a\u6b21\u3078
|
||||
PrompDialogManager.buttonType.showTimeline=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u8868\u793a
|
||||
PrompDialogManager.buttonType.update=\u66f4\u65b0
|
||||
PromptDialogManager.confirmDuringIngest.contentText=\u6b21\u3078\u9032\u307f\u307e\u3059\u304b\uff1f
|
||||
PromptDialogManager.confirmDuringIngest.headerText=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b8c\u4e86\u3059\u308b\u524d\u306b\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u8868\u793a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u307e\u3059\u3002\n\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u304c\u5b8c\u6210\u3057\u3066\u3044\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
|
||||
PromptDialogManager.progressDialog.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u3092\u5165\u529b\u4e2d
|
||||
PromptDialogManager.rebuildPrompt.details=\u8a73\u7d30\uff1a
|
||||
PromptDialogManager.rebuildPrompt.headerText=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u4e0d\u5b8c\u5168\u307e\u305f\u306f\u6700\u65b0\u3067\u306f\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002\n \u6b20\u843d\u3057\u3066\u3044\u308b\u307e\u305f\u306f\u4e0d\u6b63\u78ba\u306a\u30a4\u30d9\u30f3\u30c8\u304c\u4e00\u90e8\u3042\u308b\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002\u4e00\u90e8\u306e\u6a5f\u80fd\u304c\u5229\u7528\u3067\u304d\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
|
||||
Timeline.confirmation.dialogs.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f
|
||||
Timeline.pushDescrLOD.confdlg.title=\u8aac\u660e\u306e\u8a18\u8ff0\u30ec\u30d9\u30eb\u3092\u5909\u66f4\u3057\u307e\u3059\u304b\uff1f
|
||||
TimeLineController.errorTitle=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a8\u30e9\u30fc
|
||||
TimeLineController.outOfDate.errorMessage=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u304c\u6700\u65b0\u304b\u78ba\u8a8d\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u66f4\u65b0\u304c\u5fc5\u8981\u3060\u3068\u60f3\u5b9a\u3057\u307e\u3059\u3002
|
||||
TimeLineController.rebuildReasons.incompleteOldSchema=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a4\u30d9\u30f3\u30c8\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u4e0d\u5b8c\u5168\u306a\u60c5\u5831\u304c\u4ee5\u524d\u5165\u529b\u3055\u308c\u3066\u3044\u307e\u3057\u305f\uff1a\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u66f4\u65b0\u3057\u306a\u3044\u3068\u3001\u4e00\u90e8\u306e\u6a5f\u80fd\u304c\u5229\u7528\u3067\u304d\u306a\u3044\u3001\u307e\u305f\u306f\u6a5f\u80fd\u3057\u306a\u3044\u304b\u3082\u3057\u308c\u306a\u3044\u3067\u3059\u3002
|
||||
TimeLineController.rebuildReasons.ingestWasRunning=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b9f\u884c\u4e2d\u306b\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a4\u30d9\u30f3\u30c8\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u60c5\u5831\u304c\u5165\u529b\u3055\u308c\u3066\u3044\u307e\u3057\u305f\uff1a\u30a4\u30d9\u30f3\u30c8\u304c\u6b20\u3051\u3066\u3044\u308b\u3001\u4e0d\u5b8c\u5168\u3001\u307e\u305f\u306f\u4e0d\u6b63\u78ba\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
|
||||
TimeLineController.rebuildReasons.outOfDate=\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u304c\u6700\u65b0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\uff1a\u898b\u308c\u306a\u3044\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u3059\u3002
|
||||
TimeLineController.rebuildReasons.outOfDateError=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u304c\u6700\u65b0\u304b\u78ba\u8a8d\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
|
||||
TimeLinecontroller.updateNowQuestion=\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u4eca\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f
|
||||
TimelinePanel.jButton13.text=\u5168\u3066
|
||||
Timeline.yearBarChart.x.years=\u5E74
|
||||
TimelinePanel.jButton1.text=6\u30F6\u6708
|
||||
Timeline.yearBarChart.x.years=\u5e74
|
||||
TimelinePanel.jButton1.text=6\u30f6\u6708
|
||||
TimelinePanel.jButton10.text=1\u6642\u9593
|
||||
TimelinePanel.jButton9.text=12\u6642\u9593
|
||||
TimelinePanel.jButton11.text=5\u5E74
|
||||
TimelinePanel.jButton12.text=10\u5E74
|
||||
TimelinePanel.jButton11.text=5\u5e74
|
||||
TimelinePanel.jButton12.text=10\u5e74
|
||||
TimelinePanel.jButton6.text=1\u9031\u9593
|
||||
TimelinePanel.jButton5.text=1\u5E74
|
||||
TimelinePanel.jButton8.text=1\u65E5
|
||||
TimelinePanel.jButton7.text=3\u65E5
|
||||
TimelinePanel.jButton2.text=1\u30F6\u6708
|
||||
TimelinePanel.jButton3.text=3\u30F6\u6708
|
||||
TimelinePanel.jButton4.text=2\u9031\u9593
|
||||
TimelinePanel.jButton5.text=1\u5e74
|
||||
TimelinePanel.jButton8.text=1\u65e5
|
||||
TimelinePanel.jButton7.text=3\u65e5
|
||||
TimelinePanel.jButton2.text=1\u30f6\u6708
|
||||
TimelinePanel.jButton3.text=3\u30f6\u6708
|
||||
TimelinePanel.jButton4.text=2\u9031\u9593
|
||||
TimeLineResultView.startDateToEndDate.text={0}\u304b\u3089{1}
|
@ -24,6 +24,7 @@ import java.io.IOException;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -188,7 +189,7 @@ public class TimeLineController {
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
private TimeLineTopComponent mainFrame;
|
||||
private TimeLineTopComponent topComponent;
|
||||
|
||||
//are the listeners currently attached
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
@ -199,11 +200,7 @@ public class TimeLineController {
|
||||
private final PropertyChangeListener ingestModuleListener = new AutopsyIngestModuleListener();
|
||||
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper<VisualizationMode> visualizationMode = new ReadOnlyObjectWrapper<>(VisualizationMode.COUNTS);
|
||||
|
||||
synchronized public ReadOnlyObjectProperty<VisualizationMode> visualizationModeProperty() {
|
||||
return visualizationMode.getReadOnlyProperty();
|
||||
}
|
||||
private final ReadOnlyObjectWrapper<ViewMode> viewMode = new ReadOnlyObjectWrapper<>(ViewMode.COUNTS);
|
||||
|
||||
@GuardedBy("filteredEvents")
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
@ -223,21 +220,38 @@ public class TimeLineController {
|
||||
@GuardedBy("this")
|
||||
private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>synchronizedObservableList(FXCollections.<Long>observableArrayList());
|
||||
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true);
|
||||
|
||||
private final PromptDialogManager promptDialogManager = new PromptDialogManager(this);
|
||||
|
||||
/**
|
||||
* @return A list of the selected event ids
|
||||
* Get an ObservableList of selected event IDs
|
||||
*
|
||||
* @return A list of the selected event IDs
|
||||
*/
|
||||
synchronized public ObservableList<Long> getSelectedEventIDs() {
|
||||
return selectedEventIDs;
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
|
||||
/**
|
||||
* Get a read only observable view of the selected time range.
|
||||
*
|
||||
* @return A read only view of the selected time range.
|
||||
*/
|
||||
synchronized public ReadOnlyObjectProperty<Interval> selectedTimeRangeProperty() {
|
||||
return selectedTimeRange.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a read only view of the selected interval.
|
||||
* Get the selected time range.
|
||||
*
|
||||
* @return The selected time range.
|
||||
*/
|
||||
synchronized public ReadOnlyObjectProperty<Interval> getSelectedTimeRange() {
|
||||
return selectedTimeRange.getReadOnlyProperty();
|
||||
synchronized public Interval getSelectedTimeRange() {
|
||||
return selectedTimeRange.get();
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty eventsDBStaleProperty() {
|
||||
@ -282,9 +296,30 @@ public class TimeLineController {
|
||||
synchronized public ReadOnlyBooleanProperty canRetreatProperty() {
|
||||
return historyManager.getCanRetreat();
|
||||
}
|
||||
private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true);
|
||||
|
||||
private final PromptDialogManager promptDialogManager = new PromptDialogManager(this);
|
||||
synchronized public ReadOnlyObjectProperty<ViewMode> viewModeProperty() {
|
||||
return viewMode.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new ViewMode as the active one.
|
||||
*
|
||||
* @param viewMode The new ViewMode to set.
|
||||
*/
|
||||
synchronized public void setViewMode(ViewMode viewMode) {
|
||||
if (this.viewMode.get() != viewMode) {
|
||||
this.viewMode.set(viewMode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently active ViewMode.
|
||||
*
|
||||
* @return The currently active ViewMode.
|
||||
*/
|
||||
synchronized public ViewMode getViewMode() {
|
||||
return viewMode.get();
|
||||
}
|
||||
|
||||
public TimeLineController(Case autoCase) throws IOException {
|
||||
this.autoCase = autoCase;
|
||||
@ -310,6 +345,9 @@ public class TimeLineController {
|
||||
filteredEvents.filterProperty().get(),
|
||||
DescriptionLoD.SHORT);
|
||||
historyManager.advance(InitialZoomState);
|
||||
|
||||
//clear the selected events when the view mode changes
|
||||
viewMode.addListener(observable -> selectEventIDs(Collections.emptySet()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -449,9 +487,9 @@ public class TimeLineController {
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
|
||||
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
|
||||
Case.removePropertyChangeListener(caseListener);
|
||||
if (mainFrame != null) {
|
||||
mainFrame.close();
|
||||
mainFrame = null;
|
||||
if (topComponent != null) {
|
||||
topComponent.close();
|
||||
topComponent = null;
|
||||
}
|
||||
OpenTimelineAction.invalidateController();
|
||||
}
|
||||
@ -582,17 +620,6 @@ public class TimeLineController {
|
||||
pushTimeRange(new Interval(start, end));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new Visualization mode as the active one.
|
||||
*
|
||||
* @param visualizationMode The new VisaualizationMode to set.
|
||||
*/
|
||||
synchronized public void setVisualizationMode(VisualizationMode visualizationMode) {
|
||||
if (this.visualizationMode.get() != visualizationMode) {
|
||||
this.visualizationMode.set(visualizationMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void selectEventIDs(Collection<Long> events) {
|
||||
final LoggedTask<Interval> selectEventIDsTask = new LoggedTask<Interval>("Select Event IDs", true) { //NON-NLS
|
||||
@Override
|
||||
@ -624,16 +651,16 @@ public class TimeLineController {
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
synchronized private void showWindow() {
|
||||
if (mainFrame == null) {
|
||||
mainFrame = new TimeLineTopComponent(this);
|
||||
if (topComponent == null) {
|
||||
topComponent = new TimeLineTopComponent(this);
|
||||
}
|
||||
mainFrame.open();
|
||||
mainFrame.toFront();
|
||||
topComponent.open();
|
||||
topComponent.toFront();
|
||||
/*
|
||||
* Make this top component active so its ExplorerManager's lookup gets
|
||||
* proxied in Utilities.actionsGlobalContext()
|
||||
*/
|
||||
mainFrame.requestActive();
|
||||
topComponent.requestActive();
|
||||
}
|
||||
|
||||
synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) {
|
||||
|
@ -54,7 +54,7 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javafx.embed.swing.JFXPanel" name="jFXVizPanel">
|
||||
<Container class="javafx.embed.swing.JFXPanel" name="jFXViewPanel">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
|
||||
<JSplitPaneConstraints position="left"/>
|
||||
@ -65,7 +65,7 @@
|
||||
<Property name="useNullLayout" type="boolean" value="true"/>
|
||||
</Layout>
|
||||
</Container>
|
||||
<Container class="javax.swing.JSplitPane" name="lowerSplitXPane">
|
||||
<Container class="javax.swing.JSplitPane" name="horizontalSplitPane">
|
||||
<Properties>
|
||||
<Property name="dividerLocation" type="int" value="600"/>
|
||||
<Property name="resizeWeight" type="double" value="0.5"/>
|
||||
@ -82,33 +82,45 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="resultContainerPanel">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[700, 300]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Container class="javax.swing.JPanel" name="leftFillerPanel">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
|
||||
<JSplitPaneConstraints position="left"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="599" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="54" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="contentViewerContainerPanel">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[500, 300]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Container class="javax.swing.JPanel" name="rightfillerPanel">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
|
||||
<JSplitPaneConstraints position="right"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="364" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="54" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,12 +18,14 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.beans.PropertyVetoException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.control.Tab;
|
||||
@ -34,8 +36,15 @@ import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.controlsfx.control.Notifications;
|
||||
import org.joda.time.Interval;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.openide.explorer.ExplorerManager;
|
||||
import org.openide.explorer.ExplorerUtils;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.windows.Mode;
|
||||
import org.openide.windows.TopComponent;
|
||||
@ -44,19 +53,22 @@ import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataContentPanel;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventNode;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
||||
import org.sleuthkit.autopsy.timeline.ui.HistoryToolBar;
|
||||
import org.sleuthkit.autopsy.timeline.ui.StatusBar;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.ViewFrame;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
|
||||
import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* TopComponent for the timeline feature.
|
||||
* TopComponent for the Timeline feature.
|
||||
*/
|
||||
@TopComponent.Description(
|
||||
preferredID = "TimeLineTopComponent",
|
||||
@ -67,91 +79,209 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName());
|
||||
|
||||
private final DataContentPanel dataContentPanel;
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
private final DataContentPanel contentViewerPanel;
|
||||
|
||||
private final TimeLineResultView tlrv;
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
private DataResultPanel dataResultPanel;
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
private final ExplorerManager em = new ExplorerManager();
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
/**
|
||||
* Listener that drives the result viewer or content viewer (depending on
|
||||
* view mode) according to the controller's selected event IDs
|
||||
*/
|
||||
@NbBundle.Messages({"TimelineTopComponent.selectedEventListener.errorMsg=There was a problem getting the content for the selected event."})
|
||||
private final InvalidationListener selectedEventsListener = new InvalidationListener() {
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
ObservableList<Long> selectedEventIDs = controller.getSelectedEventIDs();
|
||||
|
||||
//depending on the active view mode, we either update the dataResultPanel, or update the contentViewerPanel directly.
|
||||
switch (controller.getViewMode()) {
|
||||
case LIST:
|
||||
|
||||
//make an array of EventNodes for the selected events
|
||||
EventNode[] childArray = new EventNode[selectedEventIDs.size()];
|
||||
try {
|
||||
for (int i = 0; i < selectedEventIDs.size(); i++) {
|
||||
childArray[i] = EventNode.createEventNode(selectedEventIDs.get(i), controller.getEventsModel());
|
||||
}
|
||||
Children children = new Children.Array();
|
||||
children.add(childArray);
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
//set generic container node as root context
|
||||
em.setRootContext(new AbstractNode(children));
|
||||
try {
|
||||
//set selected nodes for actions
|
||||
em.setSelectedNodes(childArray);
|
||||
} catch (PropertyVetoException ex) {
|
||||
//I don't know why this would ever happen.
|
||||
LOGGER.log(Level.SEVERE, "Selecting the event node was vetoed.", ex); // NON-NLS
|
||||
}
|
||||
//if there is only one event selected push it into content viewer.
|
||||
if (selectedEventIDs.size() == 1) {
|
||||
contentViewerPanel.setNode(childArray[0]);
|
||||
} else {
|
||||
contentViewerPanel.setNode(null);
|
||||
}
|
||||
});
|
||||
} catch (IllegalStateException ex) {
|
||||
//Since the case is closed, the user probably doesn't care about this, just log it as a precaution.
|
||||
LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
Notifications.create()
|
||||
.owner(jFXViewPanel.getScene().getWindow())
|
||||
.text(Bundle.TimelineTopComponent_selectedEventListener_errorMsg())
|
||||
.showError();
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case COUNTS:
|
||||
case DETAIL:
|
||||
//make a root node with nodes for the selected events as children and push it to the result viewer.
|
||||
EventRootNode rootNode = new EventRootNode(selectedEventIDs, controller.getEventsModel());
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dataResultPanel.setPath(getResultViewerSummaryString());
|
||||
dataResultPanel.setNode(rootNode);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown view mode: " + controller.getViewMode());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The TimeLineController for this topcomponent.
|
||||
*/
|
||||
public TimeLineTopComponent(TimeLineController controller) {
|
||||
initComponents();
|
||||
this.controller = controller;
|
||||
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
|
||||
|
||||
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
|
||||
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
|
||||
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
|
||||
|
||||
dataContentPanel = DataContentPanel.createInstance();
|
||||
this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER);
|
||||
tlrv = new TimeLineResultView(controller, dataContentPanel);
|
||||
DataResultPanel dataResultPanel = tlrv.getDataResultPanel();
|
||||
this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
|
||||
dataResultPanel.open();
|
||||
customizeFXComponents();
|
||||
}
|
||||
this.controller = controller;
|
||||
|
||||
@NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events",
|
||||
"TimeLineTopComponent.filterTab.name=Filters"})
|
||||
void customizeFXComponents() {
|
||||
Platform.runLater(() -> {
|
||||
//create linked result and content views
|
||||
contentViewerPanel = DataContentPanel.createInstance();
|
||||
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, contentViewerPanel);
|
||||
|
||||
//create and wire up jfx componenets that make up the interface
|
||||
final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller));
|
||||
filterTab.setClosable(false);
|
||||
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
|
||||
//add them to bottom splitpane
|
||||
horizontalSplitPane.setLeftComponent(dataResultPanel);
|
||||
horizontalSplitPane.setRightComponent(contentViewerPanel);
|
||||
|
||||
final EventsTree eventsTree = new EventsTree(controller);
|
||||
final VisualizationPanel visualizationPanel = new VisualizationPanel(controller, eventsTree);
|
||||
final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree);
|
||||
eventsTreeTab.setClosable(false);
|
||||
eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
|
||||
eventsTreeTab.disableProperty().bind(controller.visualizationModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
dataResultPanel.open(); //get the explorermanager
|
||||
|
||||
final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab);
|
||||
VBox.setVgrow(leftTabPane, Priority.ALWAYS);
|
||||
controller.visualizationModeProperty().addListener((Observable observable) -> {
|
||||
if (controller.visualizationModeProperty().get().equals(VisualizationMode.COUNTS)) {
|
||||
//if view mode is counts, make sure events tabd is not active
|
||||
leftTabPane.getSelectionModel().select(filterTab);
|
||||
}
|
||||
});
|
||||
Platform.runLater(this::initFXComponents);
|
||||
|
||||
HistoryToolBar historyToolBar = new HistoryToolBar(controller);
|
||||
final TimeZonePanel timeZonePanel = new TimeZonePanel();
|
||||
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
|
||||
//set up listeners
|
||||
TimeLineController.getTimeZone().addListener(timeZone -> dataResultPanel.setPath(getResultViewerSummaryString()));
|
||||
controller.getSelectedEventIDs().addListener(selectedEventsListener);
|
||||
|
||||
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
|
||||
|
||||
final VBox leftVBox = new VBox(5, timeZonePanel,historyToolBar, zoomSettingsPane, leftTabPane);
|
||||
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
|
||||
|
||||
final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel);
|
||||
mainSplitPane.setDividerPositions(0);
|
||||
|
||||
final Scene scene = new Scene(mainSplitPane);
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED,
|
||||
(KeyEvent event) -> {
|
||||
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(null);
|
||||
//Listen to ViewMode and adjust GUI componenets as needed.
|
||||
controller.viewModeProperty().addListener(viewMode -> {
|
||||
switch (controller.getViewMode()) {
|
||||
case COUNTS:
|
||||
case DETAIL:
|
||||
/*
|
||||
* For counts and details mode, restore the result table at
|
||||
* the bottom left.
|
||||
*/
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
splitYPane.remove(contentViewerPanel);
|
||||
if ((horizontalSplitPane.getParent() == splitYPane) == false) {
|
||||
splitYPane.setBottomComponent(horizontalSplitPane);
|
||||
horizontalSplitPane.setRightComponent(contentViewerPanel);
|
||||
}
|
||||
});
|
||||
|
||||
//add ui componenets to JFXPanels
|
||||
jFXVizPanel.setScene(scene);
|
||||
jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
|
||||
|
||||
break;
|
||||
case LIST:
|
||||
/*
|
||||
* For list mode, remove the result table, and let the
|
||||
* content viewer expand across the bottom.
|
||||
*/
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
splitYPane.setBottomComponent(contentViewerPanel);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and wire up JavaFX components of the interface
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"TimeLineTopComponent.eventsTab.name=Events",
|
||||
"TimeLineTopComponent.filterTab.name=Filters"})
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
void initFXComponents() {
|
||||
/////init componenets of left most column from top to bottom
|
||||
final TimeZonePanel timeZonePanel = new TimeZonePanel();
|
||||
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
|
||||
HistoryToolBar historyToolBar = new HistoryToolBar(controller);
|
||||
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
|
||||
|
||||
//set up filter tab
|
||||
final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller));
|
||||
filterTab.setClosable(false);
|
||||
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
|
||||
|
||||
//set up events tab
|
||||
final EventsTree eventsTree = new EventsTree(controller);
|
||||
final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree);
|
||||
eventsTreeTab.setClosable(false);
|
||||
eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
|
||||
eventsTreeTab.disableProperty().bind(controller.viewModeProperty().isNotEqualTo(ViewMode.DETAIL));
|
||||
|
||||
final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab);
|
||||
VBox.setVgrow(leftTabPane, Priority.ALWAYS);
|
||||
controller.viewModeProperty().addListener(viewMode -> {
|
||||
if (controller.getViewMode().equals(ViewMode.DETAIL) == false) {
|
||||
//if view mode is not details, switch back to the filter tab
|
||||
leftTabPane.getSelectionModel().select(filterTab);
|
||||
}
|
||||
});
|
||||
|
||||
//assemble left column
|
||||
final VBox leftVBox = new VBox(5, timeZonePanel, historyToolBar, zoomSettingsPane, leftTabPane);
|
||||
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
|
||||
|
||||
final ViewFrame viewFrame = new ViewFrame(controller, eventsTree);
|
||||
final SplitPane mainSplitPane = new SplitPane(leftVBox, viewFrame);
|
||||
mainSplitPane.setDividerPositions(0);
|
||||
|
||||
final Scene scene = new Scene(mainSplitPane);
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
|
||||
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(keyEvent)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(keyEvent)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(keyEvent)) {
|
||||
new Forward(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(keyEvent)) {
|
||||
new Forward(controller).handle(null);
|
||||
}
|
||||
});
|
||||
|
||||
//add ui componenets to JFXPanels
|
||||
jFXViewPanel.setScene(scene);
|
||||
jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Mode> availableModes(List<Mode> modes) {
|
||||
return Collections.emptyList();
|
||||
@ -165,12 +295,12 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
jFXstatusPanel = new JFXPanel();
|
||||
jFXstatusPanel = new javafx.embed.swing.JFXPanel();
|
||||
splitYPane = new javax.swing.JSplitPane();
|
||||
jFXVizPanel = new JFXPanel();
|
||||
lowerSplitXPane = new javax.swing.JSplitPane();
|
||||
resultContainerPanel = new javax.swing.JPanel();
|
||||
contentViewerContainerPanel = new javax.swing.JPanel();
|
||||
jFXViewPanel = new javafx.embed.swing.JFXPanel();
|
||||
horizontalSplitPane = new javax.swing.JSplitPane();
|
||||
leftFillerPanel = new javax.swing.JPanel();
|
||||
rightfillerPanel = new javax.swing.JPanel();
|
||||
|
||||
jFXstatusPanel.setPreferredSize(new java.awt.Dimension(100, 16));
|
||||
|
||||
@ -178,32 +308,47 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
splitYPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
|
||||
splitYPane.setResizeWeight(0.9);
|
||||
splitYPane.setPreferredSize(new java.awt.Dimension(1024, 400));
|
||||
splitYPane.setLeftComponent(jFXVizPanel);
|
||||
splitYPane.setLeftComponent(jFXViewPanel);
|
||||
|
||||
lowerSplitXPane.setDividerLocation(600);
|
||||
lowerSplitXPane.setResizeWeight(0.5);
|
||||
lowerSplitXPane.setPreferredSize(new java.awt.Dimension(1200, 300));
|
||||
lowerSplitXPane.setRequestFocusEnabled(false);
|
||||
horizontalSplitPane.setDividerLocation(600);
|
||||
horizontalSplitPane.setResizeWeight(0.5);
|
||||
horizontalSplitPane.setPreferredSize(new java.awt.Dimension(1200, 300));
|
||||
horizontalSplitPane.setRequestFocusEnabled(false);
|
||||
|
||||
resultContainerPanel.setPreferredSize(new java.awt.Dimension(700, 300));
|
||||
resultContainerPanel.setLayout(new java.awt.BorderLayout());
|
||||
lowerSplitXPane.setLeftComponent(resultContainerPanel);
|
||||
javax.swing.GroupLayout leftFillerPanelLayout = new javax.swing.GroupLayout(leftFillerPanel);
|
||||
leftFillerPanel.setLayout(leftFillerPanelLayout);
|
||||
leftFillerPanelLayout.setHorizontalGroup(
|
||||
leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 599, Short.MAX_VALUE)
|
||||
);
|
||||
leftFillerPanelLayout.setVerticalGroup(
|
||||
leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 54, Short.MAX_VALUE)
|
||||
);
|
||||
|
||||
contentViewerContainerPanel.setPreferredSize(new java.awt.Dimension(500, 300));
|
||||
contentViewerContainerPanel.setLayout(new java.awt.BorderLayout());
|
||||
lowerSplitXPane.setRightComponent(contentViewerContainerPanel);
|
||||
horizontalSplitPane.setLeftComponent(leftFillerPanel);
|
||||
|
||||
splitYPane.setRightComponent(lowerSplitXPane);
|
||||
javax.swing.GroupLayout rightfillerPanelLayout = new javax.swing.GroupLayout(rightfillerPanel);
|
||||
rightfillerPanel.setLayout(rightfillerPanelLayout);
|
||||
rightfillerPanelLayout.setHorizontalGroup(
|
||||
rightfillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 364, Short.MAX_VALUE)
|
||||
);
|
||||
rightfillerPanelLayout.setVerticalGroup(
|
||||
rightfillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 54, Short.MAX_VALUE)
|
||||
);
|
||||
|
||||
horizontalSplitPane.setRightComponent(rightfillerPanel);
|
||||
|
||||
splitYPane.setRightComponent(horizontalSplitPane);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(splitYPane, javax.swing.GroupLayout.DEFAULT_SIZE, 972, Short.MAX_VALUE)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(0, 0, 0)
|
||||
.addComponent(jFXstatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGap(0, 0, 0))
|
||||
.addComponent(jFXstatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
@ -215,11 +360,11 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JPanel contentViewerContainerPanel;
|
||||
private javafx.embed.swing.JFXPanel jFXVizPanel;
|
||||
private javax.swing.JSplitPane horizontalSplitPane;
|
||||
private javafx.embed.swing.JFXPanel jFXViewPanel;
|
||||
private javafx.embed.swing.JFXPanel jFXstatusPanel;
|
||||
private javax.swing.JSplitPane lowerSplitXPane;
|
||||
private javax.swing.JPanel resultContainerPanel;
|
||||
private javax.swing.JPanel leftFillerPanel;
|
||||
private javax.swing.JPanel rightfillerPanel;
|
||||
private javax.swing.JSplitPane splitYPane;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
@ -229,25 +374,34 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
putClientProperty(PROP_UNDOCKING_DISABLED, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentClosed() {
|
||||
// TODO add custom code on component closing
|
||||
}
|
||||
|
||||
void writeProperties(java.util.Properties p) {
|
||||
// better to version settings since initial version as advocated at
|
||||
// http://wiki.apidesign.org/wiki/PropertyFiles
|
||||
p.setProperty("version", "1.0");
|
||||
// TODO store your settings
|
||||
}
|
||||
|
||||
void readProperties(java.util.Properties p) {
|
||||
String version = p.getProperty("version");
|
||||
// TODO read your settings according to their version
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExplorerManager getExplorerManager() {
|
||||
return em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string that should be used as the label above the result table.
|
||||
* It displays the time range spanned by the selected events.
|
||||
*
|
||||
* @return A String representation of all the events displayed.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - start of date range",
|
||||
"# {1} - end of date range",
|
||||
"TimeLineResultView.startDateToEndDate.text={0} to {1}"})
|
||||
private String getResultViewerSummaryString() {
|
||||
Interval selectedTimeRange = controller.getSelectedTimeRange();
|
||||
if (selectedTimeRange == null) {
|
||||
return "";
|
||||
} else {
|
||||
final DateTimeFormatter zonedFormatter = TimeLineController.getZonedFormatter();
|
||||
String start = selectedTimeRange.getStart()
|
||||
.withZone(TimeLineController.getJodaTimeZone())
|
||||
.toString(zonedFormatter);
|
||||
String end = selectedTimeRange.getEnd()
|
||||
.withZone(TimeLineController.getJodaTimeZone())
|
||||
.toString(zonedFormatter);
|
||||
return Bundle.TimeLineResultView_startDateToEndDate_text(start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,9 @@ package org.sleuthkit.autopsy.timeline;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public enum VisualizationMode {
|
||||
public enum ViewMode {
|
||||
|
||||
COUNTS, DETAIL;
|
||||
COUNTS,
|
||||
DETAIL,
|
||||
LIST;
|
||||
}
|
@ -71,7 +71,7 @@ public class SaveSnapshotAsReport extends Action {
|
||||
"Timeline.ModuleName=Timeline",
|
||||
"SaveSnapShotAsReport.action.dialogs.title=Timeline",
|
||||
"SaveSnapShotAsReport.action.name.text=Snapshot Report",
|
||||
"SaveSnapShotAsReport.action.longText=Save a screen capture of the visualization as a report.",
|
||||
"SaveSnapShotAsReport.action.longText=Save a screen capture of the current view of the timeline as a report.",
|
||||
"# {0} - report file path",
|
||||
"SaveSnapShotAsReport.ReportSavedAt=Report saved at [{0}]",
|
||||
"SaveSnapShotAsReport.Success=Success",
|
||||
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-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.timeline.datamodel;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
|
||||
/**
|
||||
* A container for several events that have the same timestamp and description
|
||||
* and are backed by the same file. Used in the ListView to coalesce the file
|
||||
* system events for a file when they have the same timestamp.
|
||||
*/
|
||||
public class CombinedEvent {
|
||||
|
||||
private final long fileID;
|
||||
private final long epochMillis;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* A map from EventType to event ID.
|
||||
*/
|
||||
private final Map<EventType, Long> eventTypeMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param epochMillis The timestamp for this event, in millis from the Unix
|
||||
* epoch.
|
||||
* @param description The full description shared by all the combined events
|
||||
* @param fileID The ID of the file shared by all the combined events.
|
||||
* @param eventMap A map from EventType to event ID.
|
||||
*/
|
||||
public CombinedEvent(long epochMillis, String description, long fileID, Map<EventType, Long> eventMap) {
|
||||
this.epochMillis = epochMillis;
|
||||
this.description = description;
|
||||
eventTypeMap.putAll(eventMap);
|
||||
this.fileID = fileID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp of this event as millis from the Unix epoch.
|
||||
*
|
||||
* @return The timestamp of this event as millis from the Unix epoch.
|
||||
*/
|
||||
public long getStartMillis() {
|
||||
return epochMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full description shared by all the combined events.
|
||||
*
|
||||
* @return The full description shared by all the combined events.
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the obj ID of the file shared by the combined events.
|
||||
*
|
||||
* @return The obj ID of the file shared by the combined events.
|
||||
*/
|
||||
public long getFileID() {
|
||||
return fileID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the types of the combined events.
|
||||
*
|
||||
* @return The types of the combined events.
|
||||
*/
|
||||
public Set<EventType> getEventTypes() {
|
||||
return eventTypeMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event IDs of the combined events.
|
||||
*
|
||||
* @return The event IDs of the combined events.
|
||||
*/
|
||||
public Collection<Long> getEventIDs() {
|
||||
return eventTypeMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event ID of one event that is representative of all the combined
|
||||
* events. It can be used to look up a SingleEvent with more details, for
|
||||
* example.
|
||||
*
|
||||
* @return An arbitrary representative event ID for the combined events.
|
||||
*/
|
||||
public Long getRepresentativeEventID() {
|
||||
return eventTypeMap.values().stream().findFirst().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 53 * hash + (int) (this.fileID ^ (this.fileID >>> 32));
|
||||
hash = 53 * hash + (int) (this.epochMillis ^ (this.epochMillis >>> 32));
|
||||
hash = 53 * hash + Objects.hashCode(this.description);
|
||||
hash = 53 * hash + Objects.hashCode(this.eventTypeMap);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final CombinedEvent other = (CombinedEvent) obj;
|
||||
if (this.fileID != other.fileID) {
|
||||
return false;
|
||||
}
|
||||
if (this.epochMillis != other.epochMillis) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.description, other.description)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.eventTypeMap, other.eventTypeMap)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -21,10 +21,10 @@ package org.sleuthkit.autopsy.timeline.datamodel;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.joda.time.Interval;
|
||||
@ -108,8 +108,8 @@ public class EventCluster implements MultiEvent<EventStripe> {
|
||||
*/
|
||||
private final ImmutableSet<Long> hashHits;
|
||||
|
||||
private EventCluster(Interval spanningInterval, EventType type, Set<Long> eventIDs,
|
||||
Set<Long> hashHits, Set<Long> tagged, String description, DescriptionLoD lod,
|
||||
private EventCluster(Interval spanningInterval, EventType type, Collection<Long> eventIDs,
|
||||
Collection<Long> hashHits, Collection<Long> tagged, String description, DescriptionLoD lod,
|
||||
EventStripe parent) {
|
||||
|
||||
this.span = spanningInterval;
|
||||
@ -122,8 +122,8 @@ public class EventCluster implements MultiEvent<EventStripe> {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public EventCluster(Interval spanningInterval, EventType type, Set<Long> eventIDs,
|
||||
Set<Long> hashHits, Set<Long> tagged, String description, DescriptionLoD lod) {
|
||||
public EventCluster(Interval spanningInterval, EventType type, Collection<Long> eventIDs,
|
||||
Collection<Long> hashHits, Collection<Long> tagged, String description, DescriptionLoD lod) {
|
||||
this(spanningInterval, type, eventIDs, hashHits, tagged, description, lod, null);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-15 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -70,15 +70,15 @@ import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* This class acts as the model for a {@link TimeLineView}
|
||||
* This class acts as the model for a TimelineView
|
||||
*
|
||||
* Views can register listeners on properties returned by methods.
|
||||
*
|
||||
* This class is implemented as a filtered view into an underlying
|
||||
* {@link EventsRepository}.
|
||||
* EventsRepository.
|
||||
*
|
||||
* TODO: as many methods as possible should cache their results so as to avoid
|
||||
* unnecessary db calls through the {@link EventsRepository} -jm
|
||||
* unnecessary db calls through the EventsRepository -jm
|
||||
*
|
||||
* Concurrency Policy: repo is internally synchronized, so methods that only
|
||||
* access the repo atomically do not need further synchronization
|
||||
@ -279,7 +279,7 @@ public final class FilteredEventsModel {
|
||||
return repo.getTagCountsByTagName(eventIDsWithTags);
|
||||
}
|
||||
|
||||
public Set<Long> getEventIDs(Interval timeRange, Filter filter) {
|
||||
public List<Long> getEventIDs(Interval timeRange, Filter filter) {
|
||||
final Interval overlap;
|
||||
final RootFilter intersect;
|
||||
synchronized (this) {
|
||||
@ -290,6 +290,21 @@ public final class FilteredEventsModel {
|
||||
return repo.getEventIDs(overlap, intersect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a representation of all the events, within the given time range, that
|
||||
* pass the given filter, grouped by time and description such that file
|
||||
* system events for the same file, with the same timestamp, are combined
|
||||
* together.
|
||||
*
|
||||
* @param timeRange The Interval that all returned events must be within.
|
||||
* @param filter The Filter that all returned events must pass.
|
||||
*
|
||||
* @return A List of combined events, sorted by timestamp.
|
||||
*/
|
||||
public List<CombinedEvent> getCombinedEvents() {
|
||||
return repo.getCombinedEvents(requestedTimeRange.get(), requestedFilter.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* return the number of events that pass the requested filter and are within
|
||||
* the given time range.
|
||||
|
@ -56,6 +56,7 @@ import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.Version;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
@ -343,18 +344,29 @@ public class EventDB {
|
||||
return result;
|
||||
}
|
||||
|
||||
Set<Long> getEventIDs(Interval timeRange, RootFilter filter) {
|
||||
return getEventIDs(timeRange.getStartMillis() / 1000, timeRange.getEndMillis() / 1000, filter);
|
||||
}
|
||||
/**
|
||||
* Get the IDs of all the events within the given time range that pass the
|
||||
* given filter.
|
||||
*
|
||||
* @param timeRange The Interval that all returned events must be within.
|
||||
* @param filter The Filter that all returned events must pass.
|
||||
*
|
||||
* @return A List of event ids, sorted by timestamp of the corresponding
|
||||
* event..
|
||||
*/
|
||||
List<Long> getEventIDs(Interval timeRange, RootFilter filter) {
|
||||
Long startTime = timeRange.getStartMillis() / 1000;
|
||||
Long endTime = timeRange.getEndMillis() / 1000;
|
||||
|
||||
Set<Long> getEventIDs(Long startTime, Long endTime, RootFilter filter) {
|
||||
if (Objects.equals(startTime, endTime)) {
|
||||
endTime++;
|
||||
endTime++; //make sure end is at least 1 millisecond after start
|
||||
}
|
||||
Set<Long> resultIDs = new HashSet<>();
|
||||
|
||||
ArrayList<Long> resultIDs = new ArrayList<>();
|
||||
|
||||
DBLock.lock();
|
||||
final String query = "SELECT events.event_id AS event_id FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter); // NON-NLS
|
||||
final String query = "SELECT events.event_id AS event_id FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter)
|
||||
+ " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter) + " ORDER BY time ASC"; // NON-NLS
|
||||
try (Statement stmt = con.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(query)) {
|
||||
while (rs.next()) {
|
||||
@ -370,6 +382,55 @@ public class EventDB {
|
||||
return resultIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a representation of all the events, within the given time range, that
|
||||
* pass the given filter, grouped by time and description such that file
|
||||
* system events for the same file, with the same timestamp, are combined
|
||||
* together.
|
||||
*
|
||||
* @param timeRange The Interval that all returned events must be within.
|
||||
* @param filter The Filter that all returned events must pass.
|
||||
*
|
||||
* @return A List of combined events, sorted by timestamp.
|
||||
*/
|
||||
List<CombinedEvent> getCombinedEvents(Interval timeRange, RootFilter filter) {
|
||||
Long startTime = timeRange.getStartMillis() / 1000;
|
||||
Long endTime = timeRange.getEndMillis() / 1000;
|
||||
|
||||
if (Objects.equals(startTime, endTime)) {
|
||||
endTime++; //make sure end is at least 1 millisecond after start
|
||||
}
|
||||
|
||||
ArrayList<CombinedEvent> results = new ArrayList<>();
|
||||
|
||||
DBLock.lock();
|
||||
final String query = "SELECT full_description, time, file_id, GROUP_CONCAT(events.event_id), GROUP_CONCAT(sub_type)"
|
||||
+ " FROM events " + useHashHitTablesHelper(filter) + useTagTablesHelper(filter)
|
||||
+ " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter)
|
||||
+ " GROUP BY time,full_description, file_id ORDER BY time ASC, full_description";
|
||||
try (Statement stmt = con.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(query)) {
|
||||
while (rs.next()) {
|
||||
|
||||
//make a map from event type to event ID
|
||||
List<Long> eventIDs = SQLHelper.unGroupConcat(rs.getString("GROUP_CONCAT(events.event_id)"), Long::valueOf);
|
||||
List<EventType> eventTypes = SQLHelper.unGroupConcat(rs.getString("GROUP_CONCAT(sub_type)"), s -> RootEventType.allTypes.get(Integer.valueOf(s)));
|
||||
Map<EventType, Long> eventMap = new HashMap<>();
|
||||
for (int i = 0; i < eventIDs.size(); i++) {
|
||||
eventMap.put(eventTypes.get(i), eventIDs.get(i));
|
||||
}
|
||||
results.add(new CombinedEvent(rs.getLong("time") * 1000, rs.getString("full_description"), rs.getLong("file_id"), eventMap));
|
||||
}
|
||||
|
||||
} catch (SQLException sqlEx) {
|
||||
LOGGER.log(Level.SEVERE, "failed to execute query for combined events", sqlEx); // NON-NLS
|
||||
} finally {
|
||||
DBLock.unlock();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* this relies on the fact that no tskObj has ID 0 but 0 is the default
|
||||
* value for the datasource_id column in the events table.
|
||||
@ -583,7 +644,14 @@ public class EventDB {
|
||||
insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, event_id) values (?,?)"); //NON-NLS
|
||||
insertTagStmt = prepareStatement("INSERT OR IGNORE INTO tags (tag_id, tag_name_id,tag_name_display_name, event_id) values (?,?,?,?)"); //NON-NLS
|
||||
deleteTagStmt = prepareStatement("DELETE FROM tags WHERE tag_id = ?"); //NON-NLS
|
||||
countAllEventsStmt = prepareStatement("SELECT count(*) AS count FROM events"); //NON-NLS
|
||||
|
||||
/*
|
||||
* This SQL query is really just a select count(*), but that has
|
||||
* performance problems on very large tables unless you include
|
||||
* a where clause see http://stackoverflow.com/a/9338276/4004683
|
||||
* for more.
|
||||
*/
|
||||
countAllEventsStmt = prepareStatement("SELECT count(event_id) AS count FROM events WHERE event_id IS NOT null"); //NON-NLS
|
||||
dropEventsTableStmt = prepareStatement("DROP TABLE IF EXISTS events"); //NON-NLS
|
||||
dropHashSetHitsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_set_hits"); //NON-NLS
|
||||
dropHashSetsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_sets"); //NON-NLS
|
||||
@ -1090,12 +1158,12 @@ public class EventDB {
|
||||
private EventCluster eventClusterHelper(ResultSet rs, boolean useSubTypes, DescriptionLoD descriptionLOD, TagsFilter filter) throws SQLException {
|
||||
Interval interval = new Interval(rs.getLong("min(time)") * 1000, rs.getLong("max(time)") * 1000, TimeLineController.getJodaTimeZone());// NON-NLS
|
||||
String eventIDsString = rs.getString("event_ids");// NON-NLS
|
||||
Set<Long> eventIDs = SQLHelper.unGroupConcat(eventIDsString, Long::valueOf);
|
||||
List<Long> eventIDs = SQLHelper.unGroupConcat(eventIDsString, Long::valueOf);
|
||||
String description = rs.getString(SQLHelper.getDescriptionColumn(descriptionLOD));
|
||||
EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt("sub_type")) : BaseTypes.values()[rs.getInt("base_type")];// NON-NLS
|
||||
|
||||
Set<Long> hashHits = SQLHelper.unGroupConcat(rs.getString("hash_hits"), Long::valueOf); //NON-NLS
|
||||
Set<Long> tagged = SQLHelper.unGroupConcat(rs.getString("taggeds"), Long::valueOf); //NON-NLS
|
||||
List<Long> hashHits = SQLHelper.unGroupConcat(rs.getString("hash_hits"), Long::valueOf); //NON-NLS
|
||||
List<Long> tagged = SQLHelper.unGroupConcat(rs.getString("taggeds"), Long::valueOf); //NON-NLS
|
||||
|
||||
return new EventCluster(interval, type, eventIDs, hashHits, tagged, description, descriptionLOD);
|
||||
}
|
||||
@ -1168,7 +1236,6 @@ public class EventDB {
|
||||
return useSubTypes ? "sub_type" : "base_type"; //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
private PreparedStatement prepareStatement(String queryString) throws SQLException {
|
||||
PreparedStatement prepareStatement = con.prepareStatement(queryString);
|
||||
preparedStatements.add(prepareStatement);
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -56,6 +56,7 @@ import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.timeline.CancellationProgressTask;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
@ -214,10 +215,25 @@ public class EventsRepository {
|
||||
idToEventCache.invalidateAll();
|
||||
}
|
||||
|
||||
public Set<Long> getEventIDs(Interval timeRange, RootFilter filter) {
|
||||
public List<Long> getEventIDs(Interval timeRange, RootFilter filter) {
|
||||
return eventDB.getEventIDs(timeRange, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a representation of all the events, within the given time range, that
|
||||
* pass the given filter, grouped by time and description such that file
|
||||
* system events for the same file, with the same timestamp, are combined
|
||||
* together.
|
||||
*
|
||||
* @param timeRange The Interval that all returned events must be within.
|
||||
* @param filter The Filter that all returned events must pass.
|
||||
*
|
||||
* @return A List of combined events, sorted by timestamp.
|
||||
*/
|
||||
public List<CombinedEvent> getCombinedEvents(Interval timeRange, RootFilter filter) {
|
||||
return eventDB.getCombinedEvents(timeRange, filter);
|
||||
}
|
||||
|
||||
public Interval getSpanningInterval(Collection<Long> eventIDs) {
|
||||
return eventDB.getSpanningInterval(eventIDs);
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.timeline.db;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@ -82,11 +81,11 @@ class SQLHelper {
|
||||
* @return a Set of X, each element mapped from one element of the original
|
||||
* comma delimited string
|
||||
*/
|
||||
static <X> Set<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
|
||||
return StringUtils.isBlank(groupConcat) ? Collections.emptySet()
|
||||
static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
|
||||
return StringUtils.isBlank(groupConcat) ? Collections.emptyList()
|
||||
: Stream.of(groupConcat.split(","))
|
||||
.map(mapper::apply)
|
||||
.collect(Collectors.toSet());
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.timeline.events;
|
||||
|
||||
/**
|
||||
* A "local" event published by filteredEventsModel to indicate that the user
|
||||
* requested that the current visualization be refreshed with out changing any
|
||||
* requested that the current view be refreshed with out changing any
|
||||
* of the parameters ( to include more up to date tag data for example.)
|
||||
*
|
||||
* This event is not intended for use out side of the Timeline module.
|
||||
|
@ -1 +1 @@
|
||||
EventRoodNode.tooManyNode.displayName=\u8868\u793A\u3059\u308B\u30A4\u30D9\u30F3\u30C8\u6570\u304C\u591A\u3059\u304E\u307E\u3059\u3002\u6700\u5927 \= {0}\u3002\u8868\u793A\u3059\u308B\u30A4\u30D9\u30F3\u30C8\u306F{1}\u3042\u308A\u307E\u3059\u3002
|
||||
EventRoodNode.tooManyNode.displayName=\u8868\u793a\u3059\u308b\u30a4\u30d9\u30f3\u30c8\u6570\u304c\u591a\u3059\u304e\u307e\u3059\u3002\u6700\u5927 \= {0}\u3002\u8868\u793a\u3059\u308b\u30a4\u30d9\u30f3\u30c8\u306f{1}\u3042\u308a\u307e\u3059\u3002
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -23,47 +23,60 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javafx.beans.Observable;
|
||||
import javax.swing.Action;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.PropertySupport;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* * Explorer Node for {@link SingleEvent}s.
|
||||
* * Explorer Node for a SingleEvent.
|
||||
*/
|
||||
class EventNode extends DisplayableItemNode {
|
||||
public class EventNode extends DisplayableItemNode {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(EventNode.class.getName());
|
||||
|
||||
private final SingleEvent e;
|
||||
private final SingleEvent event;
|
||||
|
||||
EventNode(SingleEvent eventById, AbstractFile file, BlackboardArtifact artifact) {
|
||||
super(Children.LEAF, Lookups.fixed(eventById, file, artifact));
|
||||
this.e = eventById;
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getEventType().getIconBase()); // NON-NLS
|
||||
EventNode(SingleEvent event, AbstractFile file, BlackboardArtifact artifact) {
|
||||
super(Children.LEAF, Lookups.fixed(event, file, artifact));
|
||||
this.event = event;
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + event.getEventType().getIconBase()); // NON-NLS
|
||||
}
|
||||
|
||||
EventNode(SingleEvent eventById, AbstractFile file) {
|
||||
super(Children.LEAF, Lookups.fixed(eventById, file));
|
||||
this.e = eventById;
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getEventType().getIconBase()); // NON-NLS
|
||||
EventNode(SingleEvent event, AbstractFile file) {
|
||||
super(Children.LEAF, Lookups.fixed(event, file));
|
||||
this.event = event;
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + event.getEventType().getIconBase()); // NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({
|
||||
"NodeProperty.displayName.icon=Icon",
|
||||
"NodeProperty.displayName.description=Description",
|
||||
"NodeProperty.displayName.baseType=Base Type",
|
||||
"NodeProperty.displayName.subType=Sub Type",
|
||||
"NodeProperty.displayName.known=Known",
|
||||
"NodeProperty.displayName.dateTime=Date/Time"})
|
||||
protected Sheet createSheet() {
|
||||
Sheet s = super.createSheet();
|
||||
Sheet.Set properties = s.get(Sheet.PROPERTIES);
|
||||
@ -72,28 +85,25 @@ class EventNode extends DisplayableItemNode {
|
||||
s.put(properties);
|
||||
}
|
||||
|
||||
final TimeProperty timePropery = new TimeProperty("time", "Date/Time", "time ", getDateTimeString()); // NON-NLS
|
||||
|
||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
||||
try {
|
||||
timePropery.setValue(getDateTimeString());
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
LOGGER.log(Level.SEVERE, "unexpected error setting date/time property on EventNode explorer node", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
|
||||
properties.put(new NodeProperty<>("icon", "Icon", "icon", true)); // NON-NLS //gets overridden with icon
|
||||
properties.put(timePropery);
|
||||
properties.put(new NodeProperty<>("description", "Description", "description", e.getFullDescription())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("eventBaseType", "Base Type", "base type", e.getEventType().getSuperType().getDisplayName())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("eventSubType", "Sub Type", "sub type", e.getEventType().getDisplayName())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("Known", "Known", "known", e.getKnown().toString())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("icon", Bundle.NodeProperty_displayName_icon(), "icon", true)); // NON-NLS //gets overridden with icon
|
||||
properties.put(new TimeProperty("time", Bundle.NodeProperty_displayName_dateTime(), "time ", getDateTimeString()));// NON-NLS
|
||||
properties.put(new NodeProperty<>("description", Bundle.NodeProperty_displayName_description(), "description", event.getFullDescription())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("eventBaseType", Bundle.NodeProperty_displayName_baseType(), "base type", event.getEventType().getSuperType().getDisplayName())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("eventSubType", Bundle.NodeProperty_displayName_subType(), "sub type", event.getEventType().getDisplayName())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("Known", Bundle.NodeProperty_displayName_known(), "known", event.getKnown().toString())); // NON-NLS
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time of this event as a String formated according to the
|
||||
* controller's time zone setting.
|
||||
*
|
||||
* @return The time of this event as a String formated according to the
|
||||
* controller's time zone setting.
|
||||
*/
|
||||
private String getDateTimeString() {
|
||||
return new DateTime(e.getStartMillis(), DateTimeZone.UTC).toString(TimeLineController.getZonedFormatter());
|
||||
return new DateTime(event.getStartMillis(), DateTimeZone.UTC).toString(TimeLineController.getZonedFormatter());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,7 +128,7 @@ class EventNode extends DisplayableItemNode {
|
||||
|
||||
@Override
|
||||
public <T> T accept(DisplayableItemNodeVisitor<T> dinv) {
|
||||
throw new UnsupportedOperationException("Not supported yet."); // NON-NLS //To change body of generated methods, choose Tools | Templates.
|
||||
throw new UnsupportedOperationException("Not supported yet."); // NON-NLS
|
||||
}
|
||||
|
||||
/*
|
||||
@ -134,7 +144,7 @@ class EventNode extends DisplayableItemNode {
|
||||
* We use TimeProperty instead of a normal NodeProperty to correctly display
|
||||
* the date/time when the user changes the timezone setting.
|
||||
*/
|
||||
private class TimeProperty extends PropertySupport.ReadWrite<String> {
|
||||
final private class TimeProperty extends PropertySupport.ReadWrite<String> {
|
||||
|
||||
private String value;
|
||||
|
||||
@ -147,6 +157,14 @@ class EventNode extends DisplayableItemNode {
|
||||
super(name, String.class, displayName, shortDescription);
|
||||
setValue("suppressCustomEditor", Boolean.TRUE); // remove the "..." (editing) button NON-NLS
|
||||
this.value = value;
|
||||
TimeLineController.getTimeZone().addListener(timeZone -> {
|
||||
try {
|
||||
setValue(getDateTimeString());
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unexpected error setting date/time property on EventNode explorer node", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -161,4 +179,32 @@ class EventNode extends DisplayableItemNode {
|
||||
firePropertyChange("time", oldValue, t); // NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create an EventNode from the event ID and the events
|
||||
* model.
|
||||
*
|
||||
* @param eventID The ID of the event this node is for.
|
||||
* @param eventsModel The model that provides access to the events DB.
|
||||
*
|
||||
* @return An EventNode with the file (and artifact) backing this event in
|
||||
* its lookup.
|
||||
*/
|
||||
public static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel) throws TskCoreException, IllegalStateException {
|
||||
/*
|
||||
* Look up the event by id and creata an EventNode with the appropriate
|
||||
* data in the lookup.
|
||||
*/
|
||||
final SingleEvent eventById = eventsModel.getEventById(eventID);
|
||||
|
||||
SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
||||
AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID());
|
||||
|
||||
if (eventById.getArtifactID().isPresent()) {
|
||||
BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get());
|
||||
return new EventNode(eventById, file, blackboardArtifact);
|
||||
} else {
|
||||
return new EventNode(eventById, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,22 +27,19 @@ import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Root Explorer node to represent events.
|
||||
* Root Explorer Node to represent events.
|
||||
*/
|
||||
public class EventRootNode extends DisplayableItemNode {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Since the lazy loading seems to be broken if there are more than this
|
||||
* many child events, we don't show them and just show a message showing the
|
||||
@ -50,18 +47,8 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
*/
|
||||
public static final int MAX_EVENTS_TO_DISPLAY = 5000;
|
||||
|
||||
/**
|
||||
* the number of child events
|
||||
*/
|
||||
private final int childCount;
|
||||
|
||||
public EventRootNode(String NAME, Collection<Long> fileIds, FilteredEventsModel filteredEvents) {
|
||||
public EventRootNode(Collection<Long> fileIds, FilteredEventsModel filteredEvents) {
|
||||
super(Children.create(new EventNodeChildFactory(fileIds, filteredEvents), true), Lookups.singleton(fileIds));
|
||||
|
||||
super.setName(NAME);
|
||||
super.setDisplayName(NAME);
|
||||
|
||||
childCount = fileIds.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -74,9 +61,6 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getChildCount() {
|
||||
return childCount;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO (AUT-1849): Correct or remove peristent column reordering code
|
||||
@ -95,12 +79,12 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
private static final Logger LOGGER = Logger.getLogger(EventNodeChildFactory.class.getName());
|
||||
|
||||
/**
|
||||
* list of event ids that act as keys for the child nodes.
|
||||
* List of event IDs that act as keys for the child nodes.
|
||||
*/
|
||||
private final Collection<Long> eventIDs;
|
||||
|
||||
/**
|
||||
* filteredEvents is used to lookup the events from their ids
|
||||
* filteredEvents is used to lookup the events from their IDs
|
||||
*/
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
@ -112,8 +96,8 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
@Override
|
||||
protected boolean createKeys(List<Long> toPopulate) {
|
||||
/**
|
||||
* if there are too many events, just add one id (-1) to indicate
|
||||
* this.
|
||||
* If there are too many events, just add one dummy ID (-1) to
|
||||
* indicate this.
|
||||
*/
|
||||
if (eventIDs.size() < MAX_EVENTS_TO_DISPLAY) {
|
||||
toPopulate.addAll(eventIDs);
|
||||
@ -127,34 +111,24 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
protected Node createNodeForKey(Long eventID) {
|
||||
if (eventID < 0) {
|
||||
/*
|
||||
* if the eventId is a the special value, return a node with a
|
||||
* warning that their are too many evens
|
||||
* If the eventId is a the special value ( -1 ), return a node
|
||||
* with a warning that their are too many evens
|
||||
*/
|
||||
return new TooManyNode(eventIDs.size());
|
||||
} else {
|
||||
/*
|
||||
* look up the event by id and creata an EventNode with the
|
||||
* appropriate data in the lookup.
|
||||
*/
|
||||
final SingleEvent eventById = filteredEvents.getEventById(eventID);
|
||||
try {
|
||||
SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
||||
AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID());
|
||||
if (file != null) {
|
||||
if (eventById.getArtifactID().isPresent()) {
|
||||
BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get());
|
||||
return new EventNode(eventById, file, blackboardArtifact);
|
||||
} else {
|
||||
return new EventNode(eventById, file);
|
||||
}
|
||||
} else {
|
||||
//This should never happen in normal operations
|
||||
LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent."); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
} catch (IllegalStateException | TskCoreException ex) {
|
||||
//if some how the case was closed or ther is another unspecified exception, just bail out with a warning.
|
||||
LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent.", ex); // NON-NLS
|
||||
return EventNode.createEventNode(eventID, filteredEvents);
|
||||
} catch (IllegalStateException ex) {
|
||||
//Since the case is closed, the user probably doesn't care about this, just log it as a precaution.
|
||||
LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
return null;
|
||||
} catch (TskCoreException ex) {
|
||||
/*
|
||||
* Just log it: There might be lots of these errors, and we
|
||||
* don't want to flood the user with notifications. It will
|
||||
* be obvious the UI is broken anyways
|
||||
*/
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -162,8 +136,7 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* A Node that just shows a warning message that their are too many events
|
||||
* to show
|
||||
* A Node with a warning message that their are too many events to show.
|
||||
*/
|
||||
private static class TooManyNode extends AbstractNode {
|
||||
|
||||
|
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/table.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/table.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 920 B |
@ -70,7 +70,7 @@ public class SnapShotReportWriter {
|
||||
* @param zoomParams The ZoomParams in effect when the snapshot was
|
||||
* taken.
|
||||
* @param generationDate The generation Date of the report.
|
||||
* @param snapshot A snapshot of the visualization to include in the
|
||||
* @param snapshot A snapshot of the view to include in the
|
||||
* report.
|
||||
*/
|
||||
public SnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, ZoomParams zoomParams, Date generationDate, BufferedImage snapshot) {
|
||||
|
@ -0,0 +1,372 @@
|
||||
/*
|
||||
* 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.timeline.ui;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.controlsfx.control.MaskerPane;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
|
||||
public abstract class AbstractTimeLineView extends BorderPane {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractTimeLineView.class.getName());
|
||||
|
||||
/**
|
||||
* Boolean property that holds true if the view does not show any events
|
||||
* with the current zoom and filter settings.
|
||||
*/
|
||||
private final ReadOnlyBooleanWrapper hasVisibleEvents = new ReadOnlyBooleanWrapper(true);
|
||||
|
||||
/**
|
||||
* Boolean property that holds true if the view may not represent the
|
||||
* current state of the DB, because, for example, tags have been updated but
|
||||
* the view. was not refreshed.
|
||||
*/
|
||||
private final ReadOnlyBooleanWrapper outOfDate = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
/**
|
||||
* List of Nodes to insert into the toolbar. This should be set in an
|
||||
* implementation's constructor.
|
||||
*/
|
||||
private List<Node> settingsNodes;
|
||||
|
||||
/**
|
||||
* Listener that is attached to various properties that should trigger a
|
||||
* view update when they change.
|
||||
*/
|
||||
private InvalidationListener updateListener = (Observable any) -> refresh();
|
||||
|
||||
/**
|
||||
* Task used to reload the content of this view
|
||||
*/
|
||||
private Task<Boolean> updateTask;
|
||||
|
||||
private final TimeLineController controller;
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller
|
||||
*/
|
||||
public AbstractTimeLineView(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
this.filteredEvents.registerForEvents(this);
|
||||
this.filteredEvents.zoomParametersProperty().addListener(updateListener);
|
||||
TimeLineController.getTimeZone().addListener(updateListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a RefreshRequestedEvent from the events model by updating the
|
||||
* view.
|
||||
*
|
||||
* @param event The RefreshRequestedEvent to handle.
|
||||
*/
|
||||
@Subscribe
|
||||
public void handleRefreshRequested(RefreshRequestedEvent event) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the view represent an out-of-date state of the DB. It might if, for
|
||||
* example, tags have been updated but the view was not refreshed.
|
||||
*
|
||||
* @return True if the view does not represent the current state of the DB.
|
||||
*/
|
||||
public boolean isOutOfDate() {
|
||||
return outOfDate.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a ReadOnlyBooleanProperty that holds true if this view does not
|
||||
* represent the current state of the DB>
|
||||
*
|
||||
* @return A ReadOnlyBooleanProperty that holds the out-of-date state for
|
||||
* this view.
|
||||
*/
|
||||
public ReadOnlyBooleanProperty outOfDateProperty() {
|
||||
return outOfDate.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TimelineController for this view.
|
||||
*
|
||||
* @return The TimelineController for this view.
|
||||
*/
|
||||
protected TimeLineController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh this view based on current state of zoom / filters. Primarily
|
||||
* this invokes the background ViewRefreshTask returned by
|
||||
* getUpdateTask(), which derived classes must implement.
|
||||
*
|
||||
* TODO: replace this logic with a javafx Service ? -jm
|
||||
*/
|
||||
protected final synchronized void refresh() {
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
updateTask = null;
|
||||
}
|
||||
updateTask = getNewUpdateTask();
|
||||
updateTask.stateProperty().addListener((Observable observable) -> {
|
||||
switch (updateTask.getState()) {
|
||||
case CANCELLED:
|
||||
case FAILED:
|
||||
case READY:
|
||||
case RUNNING:
|
||||
case SCHEDULED:
|
||||
break;
|
||||
case SUCCEEDED:
|
||||
try {
|
||||
this.hasVisibleEvents.set(updateTask.get());
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unexpected exception updating view", ex); //NON-NLS
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
getController().monitorTask(updateTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the FilteredEventsModel for this view.
|
||||
*
|
||||
* @return The FilteredEventsModel for this view.
|
||||
*/
|
||||
protected FilteredEventsModel getEventsModel() {
|
||||
return filteredEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new background Task that fetches the appropriate data and loads it
|
||||
* into this view.
|
||||
*
|
||||
* @return A new task to execute on a background thread to reload this view
|
||||
* with different data.
|
||||
*/
|
||||
protected abstract Task<Boolean> getNewUpdateTask();
|
||||
|
||||
/**
|
||||
* Get a List of Nodes containing settings widgets to insert into this
|
||||
* view's header.
|
||||
*
|
||||
* @return The List of settings Nodes.
|
||||
*/
|
||||
protected List<Node> getSettingsNodes() {
|
||||
return Collections.unmodifiableList(settingsNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the List of Nodes containing settings widgets to insert into this
|
||||
* view's header.
|
||||
*
|
||||
*
|
||||
* @param settingsNodes The List of Nodes containing settings widgets to
|
||||
* insert into this view's header.
|
||||
*/
|
||||
final protected void setSettingsNodes(List<Node> settingsNodes) {
|
||||
this.settingsNodes = new ArrayList<>(settingsNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of this view and any resources it holds onto.
|
||||
*/
|
||||
final synchronized void dispose() {
|
||||
//cancel and gc updateTask
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
updateTask = null;
|
||||
}
|
||||
//remvoe and gc updateListener
|
||||
this.filteredEvents.zoomParametersProperty().removeListener(updateListener);
|
||||
TimeLineController.getTimeZone().removeListener(updateListener);
|
||||
updateListener = null;
|
||||
filteredEvents.unRegisterForEvents(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Are there are any events visible in this view with the current view
|
||||
* parameters?
|
||||
*
|
||||
* @return True if there are events visible in this view with the current
|
||||
* view parameters.
|
||||
*/
|
||||
boolean hasVisibleEvents() {
|
||||
return hasVisibleEventsProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* A property that indicates whether there are any events visible in this
|
||||
* view with the current view parameters.
|
||||
*
|
||||
* @return A property that indicates whether there are any events visible in
|
||||
* this view with the current view parameters.
|
||||
*/
|
||||
ReadOnlyBooleanProperty hasVisibleEventsProperty() {
|
||||
return hasVisibleEvents.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this view out of date because, for example, tags have been updated
|
||||
* but the view was not refreshed.
|
||||
*/
|
||||
void setOutOfDate() {
|
||||
outOfDate.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data items from this chart.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
abstract protected void clearData();
|
||||
|
||||
/**
|
||||
* Base class for Tasks that refreshes a view when the view settings change.
|
||||
*
|
||||
* @param <AxisValuesType> The type of a single object that can represent
|
||||
* the range of data displayed along the X-Axis.
|
||||
*/
|
||||
protected abstract class ViewRefreshTask<AxisValuesType> extends LoggedTask<Boolean> {
|
||||
|
||||
private final Node center;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param taskName The name of this task.
|
||||
* @param logStateChanges Whether or not task state changes should be
|
||||
* logged.
|
||||
*/
|
||||
protected ViewRefreshTask(String taskName, boolean logStateChanges) {
|
||||
super(taskName, logStateChanges);
|
||||
this.center = getCenter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets initial progress value and message and shows blocking progress
|
||||
* indicator over the view. Derived Tasks should be sure to call this as
|
||||
* part of their call() implementation.
|
||||
*
|
||||
* @return True
|
||||
*
|
||||
* @throws Exception If there is an unhandled exception during the
|
||||
* background operation
|
||||
*/
|
||||
@NbBundle.Messages(value = {"ViewRefreshTask.preparing=Analyzing zoom and filter settings"})
|
||||
@Override
|
||||
protected Boolean call() throws Exception {
|
||||
updateProgress(-1, 1);
|
||||
updateMessage(Bundle.ViewRefreshTask_preparing());
|
||||
Platform.runLater(() -> {
|
||||
MaskerPane maskerPane = new MaskerPane();
|
||||
maskerPane.textProperty().bind(messageProperty());
|
||||
maskerPane.progressProperty().bind(progressProperty());
|
||||
setCenter(new StackPane(center, maskerPane));
|
||||
setCursor(Cursor.WAIT);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the horizontal axis and removes the blocking progress
|
||||
* indicator. Derived Tasks should be sure to call this as part of their
|
||||
* succeeded() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
outOfDate.set(false);
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator. Derived Tasks should be sure
|
||||
* to call this as part of their cancelled() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
super.cancelled();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator. Derived Tasks should be sure
|
||||
* to call this as part of their failed() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void failed() {
|
||||
super.failed();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator and reset the cursor to the
|
||||
* default.
|
||||
*/
|
||||
private void cleanup() {
|
||||
setCenter(center); //clear masker pane installed in call()
|
||||
setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the horizontal range that this chart will show.
|
||||
*
|
||||
* @param values A single object representing the range that this chart
|
||||
* will show.
|
||||
*/
|
||||
protected abstract void setDateValues(AxisValuesType values);
|
||||
|
||||
/**
|
||||
* Clears the chart data and sets the horizontal axis range. For use
|
||||
* within the derived implementation of the call() method.
|
||||
*
|
||||
* @param axisValues
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.NOT_UI)
|
||||
protected void resetView(AxisValuesType axisValues) {
|
||||
Platform.runLater(() -> {
|
||||
clearData();
|
||||
setDateValues(axisValues);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -18,28 +18,16 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.ui;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
@ -47,7 +35,6 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.OverrunStyle;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.Border;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.BorderStroke;
|
||||
import javafx.scene.layout.BorderStrokeStyle;
|
||||
import javafx.scene.layout.BorderWidths;
|
||||
@ -55,7 +42,6 @@ import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
@ -64,18 +50,14 @@ import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.controlsfx.control.MaskerPane;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
|
||||
/**
|
||||
* Abstract base class for TimeLineChart based visualizations.
|
||||
* Abstract base class for TimeLineChart based views.
|
||||
*
|
||||
* @param <X> The type of data plotted along the x axis
|
||||
* @param <Y> The type of data plotted along the y axis
|
||||
@ -88,36 +70,33 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
*
|
||||
* TODO: pull up common history context menu items out of derived classes? -jm
|
||||
*/
|
||||
public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, ChartType extends Region & TimeLineChart<X>> extends BorderPane {
|
||||
public abstract class AbstractTimelineChart<X, Y, NodeType extends Node, ChartType extends Region & TimeLineChart<X>> extends AbstractTimeLineView {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractVisualizationPane.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractTimelineChart.class.getName());
|
||||
|
||||
@NbBundle.Messages("AbstractVisualization.Default_Tooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.")
|
||||
private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Default_Tooltip_text());
|
||||
@NbBundle.Messages("AbstractTimelineChart.defaultTooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.")
|
||||
private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractTimelineChart_defaultTooltip_text());
|
||||
private static final Border ONLY_LEFT_BORDER = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 0, 0, 1)));
|
||||
|
||||
/**
|
||||
* Get the tool tip to use for this visualization when no more specific
|
||||
* Tooltip is needed.
|
||||
* Get the tool tip to use for this view when no more specific Tooltip is
|
||||
* needed.
|
||||
*
|
||||
* @return The default Tooltip.
|
||||
*/
|
||||
public static Tooltip getDefaultTooltip() {
|
||||
static public Tooltip getDefaultTooltip() {
|
||||
return DEFAULT_TOOLTIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean property that holds true if the visualization may not represent
|
||||
* the current state of the DB, because, for example, tags have been updated
|
||||
* but the vis. was not refreshed.
|
||||
* The nodes that are selected.
|
||||
*
|
||||
* @return An ObservableList<NodeType> of the nodes that are selected in
|
||||
* this view.
|
||||
*/
|
||||
private final ReadOnlyBooleanWrapper outOfDate = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
/**
|
||||
* Boolean property that holds true if the visualization does not show any
|
||||
* events with the current zoom and filter settings.
|
||||
*/
|
||||
private final ReadOnlyBooleanWrapper hasVisibleEvents = new ReadOnlyBooleanWrapper(true);
|
||||
protected ObservableList<NodeType> getSelectedNodes() {
|
||||
return selectedNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access to chart data via series
|
||||
@ -130,123 +109,36 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
//// replacement axis label componenets
|
||||
private final Pane specificLabelPane = new Pane(); // container for the specfic labels in the decluttered axis
|
||||
private final Pane contextLabelPane = new Pane();// container for the contextual labels in the decluttered axis
|
||||
// container for the contextual labels in the decluttered axis
|
||||
private final Region spacer = new Region();
|
||||
|
||||
/**
|
||||
* task used to reload the content of this visualization
|
||||
*/
|
||||
private Task<Boolean> updateTask;
|
||||
|
||||
final private TimeLineController controller;
|
||||
final private FilteredEventsModel filteredEvents;
|
||||
|
||||
final private ObservableList<NodeType> selectedNodes = FXCollections.observableArrayList();
|
||||
|
||||
/**
|
||||
* Listener that is attached to various properties that should trigger a vis
|
||||
* update when they change.
|
||||
*/
|
||||
private InvalidationListener updateListener = any -> refresh();
|
||||
public Pane getSpecificLabelPane() {
|
||||
return specificLabelPane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the visualization represent an out-of-date state of the DB. It might
|
||||
* if, for example, tags have been updated but the vis. was not refreshed.
|
||||
*
|
||||
* @return True if the visualization does not represent the curent state of
|
||||
* the DB.
|
||||
*/
|
||||
public boolean isOutOfDate() {
|
||||
return outOfDate.get();
|
||||
public Pane getContextLabelPane() {
|
||||
return contextLabelPane;
|
||||
}
|
||||
|
||||
public Region getSpacer() {
|
||||
return spacer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this visualization out of date because, for example, tags have been
|
||||
* updated but the vis. was not refreshed.
|
||||
*/
|
||||
void setOutOfDate() {
|
||||
outOfDate.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a ReadOnlyBooleanProperty that holds true if this visualization does
|
||||
* not represent the current state of the DB>
|
||||
* Get the CharType that implements this view.
|
||||
*
|
||||
* @return A ReadOnlyBooleanProperty that holds the out-of-date state for
|
||||
* this visualization.
|
||||
*/
|
||||
public ReadOnlyBooleanProperty outOfDateProperty() {
|
||||
return outOfDate.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* The visualization nodes that are selected.
|
||||
*
|
||||
* @return An ObservableList<NodeType> of the nodes that are selected in
|
||||
* this visualization.
|
||||
*/
|
||||
protected ObservableList<NodeType> getSelectedNodes() {
|
||||
return selectedNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of Nodes to insert into the toolbar. This should be set in an
|
||||
* implementations constructor.
|
||||
*/
|
||||
private List<Node> settingsNodes;
|
||||
|
||||
/**
|
||||
* Get a List of nodes containing settings widgets to insert into this
|
||||
* visualization's header.
|
||||
*
|
||||
* @return The List of settings Nodes.
|
||||
*/
|
||||
protected List<Node> getSettingsNodes() {
|
||||
return Collections.unmodifiableList(settingsNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the List of nodes containing settings widgets to insert into this
|
||||
* visualization's header.
|
||||
*
|
||||
*
|
||||
* @param settingsNodes The List of nodes containing settings widgets to
|
||||
* insert into this visualization's header.
|
||||
*/
|
||||
protected void setSettingsNodes(List<Node> settingsNodes) {
|
||||
this.settingsNodes = new ArrayList<>(settingsNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TimelineController for this visualization.
|
||||
*
|
||||
* @return The TimelineController for this visualization.
|
||||
*/
|
||||
protected TimeLineController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CharType that implements this visualization.
|
||||
*
|
||||
* @return The CharType that implements this visualization.
|
||||
* @return The CharType that implements this view.
|
||||
*/
|
||||
protected ChartType getChart() {
|
||||
return chart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the FilteredEventsModel for this visualization.
|
||||
* Set the ChartType that implements this view.
|
||||
*
|
||||
* @return The FilteredEventsModel for this visualization.
|
||||
*/
|
||||
protected FilteredEventsModel getEventsModel() {
|
||||
return filteredEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ChartType that implements this visualization.
|
||||
*
|
||||
* @param chart The ChartType that implements this visualization.
|
||||
* @param chart The ChartType that implements this view.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
protected void setChart(ChartType chart) {
|
||||
@ -255,29 +147,7 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
}
|
||||
|
||||
/**
|
||||
* A property that indicates whether there are any events visible in this
|
||||
* visualization with the current view parameters.
|
||||
*
|
||||
* @return A property that indicates whether there are any events visible in
|
||||
* this visualization with the current view parameters.
|
||||
*/
|
||||
ReadOnlyBooleanProperty hasVisibleEventsProperty() {
|
||||
return hasVisibleEvents.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Are there are any events visible in this visualization with the current
|
||||
* view parameters?
|
||||
*
|
||||
* @return True if there are events visible in this visualization with the
|
||||
* current view parameters.
|
||||
*/
|
||||
boolean hasVisibleEvents() {
|
||||
return hasVisibleEventsProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply this visualization's 'selection effect' to the given node.
|
||||
* Apply this view's 'selection effect' to the given node.
|
||||
*
|
||||
* @param node The node to apply the 'effect' to.
|
||||
*/
|
||||
@ -286,7 +156,7 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this visualization's 'selection effect' from the given node.
|
||||
* Remove this view's 'selection effect' from the given node.
|
||||
*
|
||||
* @param node The node to remvoe the 'effect' from.
|
||||
*/
|
||||
@ -298,7 +168,7 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
* Should the tick mark at the given value be bold, because it has
|
||||
* interesting data associated with it?
|
||||
*
|
||||
* @param value A value along this visualization's x axis
|
||||
* @param value A value along this view's x axis
|
||||
*
|
||||
* @return True if the tick label for the given value should be bold ( has
|
||||
* relevant data), false otherwise
|
||||
@ -306,8 +176,8 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
abstract protected Boolean isTickBold(X value);
|
||||
|
||||
/**
|
||||
* Apply this visualization's 'selection effect' to the given node, if
|
||||
* applied is true. If applied is false, remove the affect
|
||||
* Apply this view's 'selection effect' to the given node, if applied is
|
||||
* true. If applied is false, remove the affect
|
||||
*
|
||||
* @param node The node to apply the 'effect' to
|
||||
* @param applied True if the effect should be applied, false if the effect
|
||||
@ -315,15 +185,6 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
*/
|
||||
abstract protected void applySelectionEffect(NodeType node, Boolean applied);
|
||||
|
||||
/**
|
||||
* Get a new background Task that fetches the appropriate data and loads it
|
||||
* into this visualization.
|
||||
*
|
||||
* @return A new task to execute on a background thread to reload this
|
||||
* visualization with different data.
|
||||
*/
|
||||
abstract protected Task<Boolean> getNewUpdateTask();
|
||||
|
||||
/**
|
||||
* Get the label that should be used for a tick mark at the given value.
|
||||
*
|
||||
@ -342,16 +203,16 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
abstract protected double getTickSpacing();
|
||||
|
||||
/**
|
||||
* Get the X-Axis of this Visualization's chart
|
||||
* Get the X-Axis of this view's chart
|
||||
*
|
||||
* @return The horizontal axis used by this Visualization's chart
|
||||
* @return The horizontal axis used by this view's chart
|
||||
*/
|
||||
abstract protected Axis<X> getXAxis();
|
||||
|
||||
/**
|
||||
* Get the Y-Axis of this Visualization's chart
|
||||
* Get the Y-Axis of this view's chart
|
||||
*
|
||||
* @return The vertical axis used by this Visualization's chart
|
||||
* @return The vertical axis used by this view's chart
|
||||
*/
|
||||
abstract protected Axis<Y> getYAxis();
|
||||
|
||||
@ -364,74 +225,6 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
*/
|
||||
abstract protected double getAxisMargin();
|
||||
|
||||
/**
|
||||
* Clear all data items from this chart.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
abstract protected void clearChartData();
|
||||
|
||||
/**
|
||||
* Refresh this visualization based on current state of zoom / filters.
|
||||
* Primarily this invokes the background VisualizationUpdateTask returned by
|
||||
* getUpdateTask(), which derived classes must implement.
|
||||
*
|
||||
* TODO: replace this logic with a javafx Service ? -jm
|
||||
*/
|
||||
protected final synchronized void refresh() {
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
updateTask = null;
|
||||
}
|
||||
updateTask = getNewUpdateTask();
|
||||
updateTask.stateProperty().addListener((Observable observable) -> {
|
||||
switch (updateTask.getState()) {
|
||||
case CANCELLED:
|
||||
case FAILED:
|
||||
case READY:
|
||||
case RUNNING:
|
||||
case SCHEDULED:
|
||||
break;
|
||||
case SUCCEEDED:
|
||||
try {
|
||||
this.hasVisibleEvents.set(updateTask.get());
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unexpected exception updating visualization", ex); //NON-NLS
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
controller.monitorTask(updateTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a RefreshRequestedEvent from the events model by updating the
|
||||
* visualization.
|
||||
*
|
||||
* @param event The RefreshRequestedEvent to handle.
|
||||
*/
|
||||
@Subscribe
|
||||
public void handleRefreshRequested(RefreshRequestedEvent event) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of this visualization and any resources it holds onto.
|
||||
*/
|
||||
final synchronized void dispose() {
|
||||
|
||||
//cancel and gc updateTask
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
updateTask = null;
|
||||
}
|
||||
//remvoe and gc updateListener
|
||||
this.filteredEvents.zoomParametersProperty().removeListener(updateListener);
|
||||
TimeLineController.getTimeZone().removeListener(updateListener);
|
||||
updateListener = null;
|
||||
|
||||
filteredEvents.unRegisterForEvents(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a series for each event type in a consistent order.
|
||||
*/
|
||||
@ -459,23 +252,20 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The TimelineController for this visualization.
|
||||
* @param controller The TimelineController for this view.
|
||||
*/
|
||||
protected AbstractVisualizationPane(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
this.filteredEvents.registerForEvents(this);
|
||||
this.filteredEvents.zoomParametersProperty().addListener(updateListener);
|
||||
protected AbstractTimelineChart(TimeLineController controller) {
|
||||
super(controller);
|
||||
Platform.runLater(() -> {
|
||||
VBox vBox = new VBox(specificLabelPane, contextLabelPane);
|
||||
VBox vBox = new VBox(getSpecificLabelPane(), getContextLabelPane());
|
||||
vBox.setFillWidth(false);
|
||||
HBox hBox = new HBox(spacer, vBox);
|
||||
HBox hBox = new HBox(getSpacer(), vBox);
|
||||
hBox.setFillHeight(false);
|
||||
setBottom(hBox);
|
||||
DoubleBinding spacerSize = getYAxis().widthProperty().add(getYAxis().tickLengthProperty()).add(getAxisMargin());
|
||||
spacer.minWidthProperty().bind(spacerSize);
|
||||
spacer.prefWidthProperty().bind(spacerSize);
|
||||
spacer.maxWidthProperty().bind(spacerSize);
|
||||
getSpacer().minWidthProperty().bind(spacerSize);
|
||||
getSpacer().prefWidthProperty().bind(spacerSize);
|
||||
getSpacer().maxWidthProperty().bind(spacerSize);
|
||||
});
|
||||
|
||||
createSeries();
|
||||
@ -487,10 +277,8 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
}
|
||||
});
|
||||
|
||||
TimeLineController.getTimeZone().addListener(updateListener);
|
||||
|
||||
//show tooltip text in status bar
|
||||
hoverProperty().addListener(hoverProp -> controller.setStatusMessage(isHover() ? DEFAULT_TOOLTIP.getText() : ""));
|
||||
hoverProperty().addListener(hoverProp -> controller.setStatusMessage(isHover() ? getDefaultTooltip().getText() : ""));
|
||||
|
||||
}
|
||||
|
||||
@ -689,117 +477,4 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for Tasks that refresh a visualization when the view settings
|
||||
* change.
|
||||
*
|
||||
* @param <AxisValuesType> The type of a single object that can represent
|
||||
* the range of data displayed along the X-Axis.
|
||||
*/
|
||||
abstract protected class VisualizationRefreshTask<AxisValuesType> extends LoggedTask<Boolean> {
|
||||
|
||||
private final Node center;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param taskName The name of this task.
|
||||
* @param logStateChanges Whether or not task state changes should be
|
||||
* logged.
|
||||
*/
|
||||
protected VisualizationRefreshTask(String taskName, boolean logStateChanges) {
|
||||
super(taskName, logStateChanges);
|
||||
this.center = getCenter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets initial progress value and message and shows blocking progress
|
||||
* indicator over the visualization. Derived Tasks should be sure to
|
||||
* call this as part of their call() implementation.
|
||||
*
|
||||
* @return True
|
||||
*
|
||||
* @throws Exception If there is an unhandled exception during the
|
||||
* background operation
|
||||
*/
|
||||
@NbBundle.Messages({"VisualizationUpdateTask.preparing=Analyzing zoom and filter settings"})
|
||||
@Override
|
||||
protected Boolean call() throws Exception {
|
||||
updateProgress(-1, 1);
|
||||
updateMessage(Bundle.VisualizationUpdateTask_preparing());
|
||||
Platform.runLater(() -> {
|
||||
MaskerPane maskerPane = new MaskerPane();
|
||||
maskerPane.textProperty().bind(messageProperty());
|
||||
maskerPane.progressProperty().bind(progressProperty());
|
||||
setCenter(new StackPane(center, maskerPane));
|
||||
setCursor(Cursor.WAIT);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the horizontal axis and removes the blocking progress
|
||||
* indicator. Derived Tasks should be sure to call this as part of their
|
||||
* succeeded() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
outOfDate.set(false);
|
||||
layoutDateLabels();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator. Derived Tasks should be sure
|
||||
* to call this as part of their cancelled() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
super.cancelled();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator. Derived Tasks should be sure
|
||||
* to call this as part of their failed() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void failed() {
|
||||
super.failed();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator and reset the cursor to the
|
||||
* default.
|
||||
*/
|
||||
private void cleanup() {
|
||||
setCenter(center); //clear masker pane installed in call()
|
||||
setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the chart data and sets the horizontal axis range. For use
|
||||
* within the derived implementation of the call() method.
|
||||
*
|
||||
* @param axisValues
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.NOT_UI)
|
||||
protected void resetChart(AxisValuesType axisValues) {
|
||||
Platform.runLater(() -> {
|
||||
clearChartData();
|
||||
setDateAxisValues(axisValues);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the horizontal range that this chart will show.
|
||||
*
|
||||
* @param values A single object representing the range that this chart
|
||||
* will show.
|
||||
*/
|
||||
abstract protected void setDateAxisValues(AxisValuesType values);
|
||||
}
|
||||
}
|
@ -35,14 +35,13 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years
|
||||
Timeline.ui.ZoomRanges.fiveyears.text=Five Years
|
||||
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
|
||||
Timeline.ui.ZoomRanges.all.text=All
|
||||
TimeLineResultView.startDateToEndDate.text={0} to {1}
|
||||
VisualizationPanel.histogramTask.title=Rebuild Histogram
|
||||
VisualizationPanel.histogramTask.preparing=preparing
|
||||
VisualizationPanel.histogramTask.resetUI=resetting ui
|
||||
VisualizationPanel.histogramTask.queryDb=querying db
|
||||
VisualizationPanel.histogramTask.updateUI2=updating ui
|
||||
VisualizationPanel.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings.
|
||||
VisualizationPanel.zoomButton.text=Zoom to events
|
||||
ViewFrame.histogramTask.title=Rebuild Histogram
|
||||
ViewFrame.histogramTask.preparing=preparing
|
||||
ViewFrame.histogramTask.resetUI=resetting ui
|
||||
ViewFrame.histogramTask.queryDb=querying db
|
||||
ViewFrame.histogramTask.updateUI2=updating ui
|
||||
ViewFrame.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings.
|
||||
ViewFrame.zoomButton.text=Zoom to events
|
||||
TimeZonePanel.localRadio.text=Local Time Zone
|
||||
TimeZonePanel.otherRadio.text=GMT / UTC
|
||||
VisualizationPanel.resetFiltersButton.text=Reset all filters
|
||||
ViewFrame.resetFiltersButton.text=Reset all filters
|
||||
|
@ -1,24 +1,24 @@
|
||||
Timeline.node.root=\u30eb\u30fc\u30c8
|
||||
Timeline.ui.TimeLineChart.tooltip.text=\u30c0\u30d6\u30eb\u30af\u30ea\u30c3\u30af\u3067\u4e0b\u8a18\u306e\u7bc4\u56f2\u3078\u30ba\u30fc\u30e0\uff1a\n{0}\u301c{1}\n\u53f3\u30af\u30ea\u30c3\u30af\u3067\u5143\u306b\u623b\u308a\u307e\u3059\u3002
|
||||
Timeline.ui.ZoomRanges.all.text=\u5168\u3066
|
||||
TimeLineResultView.startDateToEndDate.text={0}\u304b\u3089{1}
|
||||
VisualizationPanel.histogramTask.preparing=\u6e96\u5099\u4e2d
|
||||
VisualizationPanel.histogramTask.queryDb=DB\u3092\u30af\u30a8\u30ea\u4e2d
|
||||
VisualizationPanel.histogramTask.resetUI=ui\u3092\u518d\u8a2d\u5b9a\u4e2d
|
||||
VisualizationPanel.histogramTask.title=\u30d2\u30b9\u30c8\u30b0\u30e9\u30e0\u3092\u518d\u30d3\u30eb\u30c9
|
||||
VisualizationPanel.histogramTask.updateUI2=ui\u3092\u66f4\u65b0\u4e2d
|
||||
|
||||
ViewFrame.histogramTask.preparing=\u6e96\u5099\u4e2d
|
||||
ViewFrame.histogramTask.queryDb=DB\u3092\u30af\u30a8\u30ea\u4e2d
|
||||
ViewFrame.histogramTask.resetUI=ui\u3092\u518d\u8a2d\u5b9a\u4e2d
|
||||
ViewFrame.histogramTask.title=\u30d2\u30b9\u30c8\u30b0\u30e9\u30e0\u3092\u518d\u30d3\u30eb\u30c9
|
||||
ViewFrame.histogramTask.updateUI2=ui\u3092\u66f4\u65b0\u4e2d
|
||||
TimeZonePanel.localRadio.text=\u30ed\u30fc\u30ab\u30eb\u30bf\u30a4\u30e0\u30be\u30fc\u30f3
|
||||
VisualizationPanel.countsToggle.text=\u30ab\u30a6\u30f3\u30c8
|
||||
VisualizationPanel.detailsToggle.text=\u8a73\u7d30
|
||||
VisualizationPanel.endLabel.text=\u30a8\u30f3\u30c9\uff1a
|
||||
VisualizationPanel.noEventsDialogLabel.text=\u73fe\u5728\u306e\u30ba\u30fc\u30e0\uff0f\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3067\u306f\u898b\u3048\u308b\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093\u3002
|
||||
VisualizationPanel.resetFiltersButton.text=\u5168\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u30fc\u3092\u30ea\u30bb\u30c3\u30c8
|
||||
VisualizationPanel.startLabel.text=\u30b9\u30bf\u30fc\u30c8\uff1a
|
||||
VisualizationPanel.visualizationModeLabel.text=\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u30e2\u30fc\u30c9\uff1a
|
||||
VisualizationPanel.zoomButton.text=\u30a4\u30d9\u30f3\u30c8\u3078\u30ba\u30fc\u30e0
|
||||
VisualizationPanel.zoomMenuButton.text=\u4e0b\u8a18\u3078\u30ba\u30fc\u30e0\u30a4\u30f3\uff0f\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8
|
||||
ViewFrame.countsToggle.text=\u30ab\u30a6\u30f3\u30c8
|
||||
ViewFrame.detailsToggle.text=\u8a73\u7d30
|
||||
ViewFrame.endLabel.text=\u30a8\u30f3\u30c9\uff1a
|
||||
ViewFrame.noEventsDialogLabel.text=\u73fe\u5728\u306e\u30ba\u30fc\u30e0\uff0f\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3067\u306f\u898b\u3048\u308b\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093\u3002
|
||||
ViewFrame.resetFiltersButton.text=\u5168\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u30fc\u3092\u30ea\u30bb\u30c3\u30c8
|
||||
ViewFrame.startLabel.text=\u30b9\u30bf\u30fc\u30c8\uff1a
|
||||
ViewFrame.viewModeLabel.text=\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u30e2\u30fc\u30c9\uff1a
|
||||
ViewFrame.zoomButton.text=\u30a4\u30d9\u30f3\u30c8\u3078\u30ba\u30fc\u30e0
|
||||
ViewFrame.zoomMenuButton.text=\u4e0b\u8a18\u3078\u30ba\u30fc\u30e0\u30a4\u30f3\uff0f\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8
|
||||
*=Autopsy\u30d5\u30a9\u30ec\u30f3\u30b8\u30c3\u30af\u30d6\u30e9\u30a6\u30b6
|
||||
AbstractVisualization.Default_Tooltip.text=\u30de\u30a6\u30b9\u3092\u30c9\u30e9\u30c3\u30b0\u3057\u3066\u30ba\u30fc\u30e0\u3059\u308b\u30bf\u30a4\u30e0\u9593\u9694\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\u305d\u306e\u4ed6\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u306f\u53f3\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f\u3060\u3055\u3044
|
||||
AbstractTimelineChart.defaultTooltip.text=\u30de\u30a6\u30b9\u3092\u30c9\u30e9\u30c3\u30b0\u3057\u3066\u30ba\u30fc\u30e0\u3059\u308b\u30bf\u30a4\u30e0\u9593\u9694\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\u305d\u306e\u4ed6\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u306f\u53f3\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f\u3060\u3055\u3044
|
||||
IntervalSelector.ClearSelectedIntervalAction.tooltTipText=\u9078\u629e\u3057\u305f\u9593\u9694\u3092\u30af\u30ea\u30a2\u3059\u308b
|
||||
IntervalSelector.ZoomAction.name=\u30ba\u30fc\u30e0
|
||||
NoEventsDialog.titledPane.text=\u898b\u308c\u308b\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093
|
||||
@ -40,6 +40,6 @@ Timeline.ui.ZoomRanges.fiveyears.text=5\u5e74
|
||||
Timeline.ui.ZoomRanges.tenyears.text=10\u5e74
|
||||
TimeLineChart.zoomHistoryActionGroup.name=\u30ba\u30fc\u30e0\u5c65\u6b74
|
||||
TimeZonePanel.title=\u6642\u9593\u3092\u6b21\u3067\u8868\u793a\uff1a
|
||||
VisualizationPanel.refresh=\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5
|
||||
VisualizationPanel.tagsAddedOrDeleted=\u30bf\u30b0\u304c\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f\u304a\u3088\u3073\u307e\u305f\u306f\u524a\u9664\u3055\u308c\u307e\u3057\u305f\u3002\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u304c\u6700\u65b0\u3067\u306f\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
|
||||
VisualizationUpdateTask.preparing=\u30ba\u30fc\u30e0\u304a\u3088\u3073\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3092\u89e3\u6790\u4e2d
|
||||
ViewFrame.refresh=\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5
|
||||
ViewFrame.tagsAddedOrDeleted=\u30bf\u30b0\u304c\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f\u304a\u3088\u3073\u307e\u305f\u306f\u524a\u9664\u3055\u308c\u307e\u3057\u305f\u3002\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u304c\u6700\u65b0\u3067\u306f\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
|
||||
ViewRefreshTask.preparing=\u30ba\u30fc\u30e0\u304a\u3088\u3073\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3092\u89e3\u6790\u4e2d
|
||||
|
@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 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;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javafx.beans.Observable;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
||||
|
||||
/**
|
||||
* Since it was too hard to derive from {@link DataResultPanel}, this class
|
||||
* implements {@link TimeLineView}, listens to the events/state of a the
|
||||
* assigned {@link FilteredEventsModel} and acts appropriately on its
|
||||
* {@link DataResultPanel}. That is, this class acts as a sort of bridge/adapter
|
||||
* between a FilteredEventsModel instance and a DataResultPanel instance.
|
||||
*/
|
||||
public class TimeLineResultView {
|
||||
|
||||
/**
|
||||
* the {@link DataResultPanel} that is the real view proxied by this class
|
||||
*/
|
||||
private final DataResultPanel dataResultPanel;
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
private Set<Long> selectedEventIDs = new HashSet<>();
|
||||
|
||||
public DataResultPanel getDataResultPanel() {
|
||||
return dataResultPanel;
|
||||
}
|
||||
|
||||
public TimeLineResultView(TimeLineController controller, DataContent dataContent) {
|
||||
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
|
||||
|
||||
//set up listeners on relevant properties
|
||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
||||
dataResultPanel.setPath(getSummaryString());
|
||||
});
|
||||
|
||||
controller.getSelectedEventIDs().addListener((Observable o) -> {
|
||||
refresh();
|
||||
});
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a String representation of all the Events displayed
|
||||
*/
|
||||
private String getSummaryString() {
|
||||
if (controller.getSelectedTimeRange().get() != null) {
|
||||
final DateTimeFormatter zonedFormatter = TimeLineController.getZonedFormatter();
|
||||
return NbBundle.getMessage(this.getClass(), "TimeLineResultView.startDateToEndDate.text",
|
||||
controller.getSelectedTimeRange().get().getStart()
|
||||
.withZone(TimeLineController.getJodaTimeZone())
|
||||
.toString(zonedFormatter),
|
||||
controller.getSelectedTimeRange().get().getEnd()
|
||||
.withZone(TimeLineController.getJodaTimeZone())
|
||||
.toString(zonedFormatter));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* refresh this view with the events selected in the controller
|
||||
*/
|
||||
public final void refresh() {
|
||||
|
||||
Set<Long> newSelectedEventIDs = new HashSet<>(controller.getSelectedEventIDs());
|
||||
if (selectedEventIDs.equals(newSelectedEventIDs) == false) {
|
||||
selectedEventIDs = newSelectedEventIDs;
|
||||
final EventRootNode root = new EventRootNode(
|
||||
NbBundle.getMessage(this.getClass(), "Timeline.node.root"), selectedEventIDs,
|
||||
filteredEvents);
|
||||
|
||||
//this must be in edt or exception is thrown
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dataResultPanel.setPath(getSummaryString());
|
||||
dataResultPanel.setNode(root);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
<items>
|
||||
<HBox alignment="CENTER_LEFT" BorderPane.alignment="CENTER" HBox.hgrow="NEVER">
|
||||
<children>
|
||||
<Label fx:id="visualizationModeLabel" text="Visualisation Mode:" textAlignment="CENTER" wrapText="true" HBox.hgrow="NEVER">
|
||||
<Label fx:id="viewModeLabel" text="View Mode:" textAlignment="CENTER" wrapText="true" HBox.hgrow="NEVER">
|
||||
<HBox.margin>
|
||||
<Insets right="5.0" />
|
||||
</HBox.margin>
|
||||
@ -32,7 +32,7 @@
|
||||
</font>
|
||||
</Label>
|
||||
|
||||
<SegmentedButton maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" HBox.hgrow="NEVER">
|
||||
<SegmentedButton fx:id="modeSegButton" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" HBox.hgrow="NEVER">
|
||||
<buttons>
|
||||
<ToggleButton fx:id="countsToggle" alignment="TOP_LEFT" mnemonicParsing="false" selected="true">
|
||||
<graphic>
|
||||
@ -58,8 +58,20 @@
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</ToggleButton>
|
||||
<ToggleButton fx:id="listToggle" alignment="CENTER_RIGHT" layoutX="74.0" mnemonicParsing="false" selected="false">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true" rotate="0.0" smooth="true" style="-fx-background-color:white;" x="2.0" y="1.0">
|
||||
<image>
|
||||
<Image url="@../images/table.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</ToggleButton>
|
||||
</buttons>
|
||||
|
||||
|
||||
</SegmentedButton>
|
||||
</children>
|
||||
<padding>
|
||||
@ -81,7 +93,7 @@
|
||||
</graphic>
|
||||
</Button>
|
||||
<Separator maxWidth="1.7976931348623157E308" orientation="VERTICAL" />
|
||||
<Button fx:id="refreshButton" alignment="CENTER_RIGHT" mnemonicParsing="false" text="Refresh Vis.">
|
||||
<Button fx:id="refreshButton" alignment="CENTER_RIGHT" mnemonicParsing="false" text="Refresh View">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
@ -29,15 +29,12 @@ import java.util.function.Supplier;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToolBar;
|
||||
import javafx.scene.control.Tooltip;
|
||||
@ -60,8 +57,10 @@ import javax.annotation.Nonnull;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import jfxtras.scene.control.LocalDateTimePicker;
|
||||
import jfxtras.scene.control.LocalDateTimeTextField;
|
||||
import jfxtras.scene.control.ToggleGroupValue;
|
||||
import org.controlsfx.control.NotificationPane;
|
||||
import org.controlsfx.control.RangeSlider;
|
||||
import org.controlsfx.control.SegmentedButton;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
@ -73,7 +72,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.ViewMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport;
|
||||
@ -88,18 +87,19 @@ import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
|
||||
import org.sleuthkit.autopsy.timeline.ui.listvew.ListViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
/**
|
||||
* A container for an AbstractVisualizationPane. Has a Toolbar on top to hold
|
||||
* settings widgets supplied by contained AbstractVisualizationPane, and the
|
||||
* A container for an AbstractTimelineView. Has a Toolbar on top to hold
|
||||
* settings widgets supplied by contained AbstractTimelineView, and the
|
||||
* histogram / time selection on bottom.
|
||||
*
|
||||
* TODO: Refactor common code out of histogram and CountsView? -jm
|
||||
*/
|
||||
final public class VisualizationPanel extends BorderPane {
|
||||
final public class ViewFrame extends BorderPane {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(VisualizationPanel.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(ViewFrame.class.getName());
|
||||
|
||||
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
|
||||
private static final Image WARNING = new Image("org/sleuthkit/autopsy/timeline/images/warning_triangle.png", 16, 16, true, true); // NON-NLS
|
||||
@ -108,7 +108,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
/**
|
||||
* Region that will be stacked in between the no-events "dialog" and the
|
||||
* hosted AbstractVisualizationPane in order to gray out the visualization.
|
||||
* hosted AbstractTimelineView in order to gray out the AbstractTimelineView.
|
||||
*/
|
||||
private final static Region NO_EVENTS_BACKGROUND = new Region() {
|
||||
{
|
||||
@ -121,7 +121,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
private LoggedTask<Void> histogramTask;
|
||||
|
||||
private final EventsTree eventsTree;
|
||||
private AbstractVisualizationPane<?, ?, ?, ?> visualization;
|
||||
private AbstractTimeLineView hostedView;
|
||||
|
||||
/*
|
||||
* HBox that contains the histogram bars.
|
||||
@ -159,12 +159,16 @@ final public class VisualizationPanel extends BorderPane {
|
||||
@FXML
|
||||
private ToolBar toolBar;
|
||||
@FXML
|
||||
private Label visualizationModeLabel;
|
||||
private Label viewModeLabel;
|
||||
@FXML
|
||||
private SegmentedButton modeSegButton;
|
||||
@FXML
|
||||
private ToggleButton countsToggle;
|
||||
@FXML
|
||||
private ToggleButton detailsToggle;
|
||||
@FXML
|
||||
private ToggleButton listToggle;
|
||||
@FXML
|
||||
private Button snapShotButton;
|
||||
@FXML
|
||||
private Button refreshButton;
|
||||
@ -172,7 +176,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
private Button updateDBButton;
|
||||
|
||||
/*
|
||||
* Wraps contained visualization so that we can show notifications over it.
|
||||
* Wraps contained AbstractTimelineView so that we can show notifications over it.
|
||||
*/
|
||||
private final NotificationPane notificationPane = new NotificationPane();
|
||||
|
||||
@ -241,25 +245,26 @@ final public class VisualizationPanel extends BorderPane {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The TimeLineController for this VisualizationPanel
|
||||
* @param eventsTree The EventsTree this VisualizationPanel hosts.
|
||||
* @param controller The TimeLineController for this ViewFrame
|
||||
* @param eventsTree The EventsTree this ViewFrame hosts.
|
||||
*/
|
||||
public VisualizationPanel(@Nonnull TimeLineController controller, @Nonnull EventsTree eventsTree) {
|
||||
public ViewFrame(@Nonnull TimeLineController controller, @Nonnull EventsTree eventsTree) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
this.eventsTree = eventsTree;
|
||||
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
|
||||
FXMLConstructor.construct(this, "ViewFrame.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({
|
||||
"VisualizationPanel.visualizationModeLabel.text=Visualization Mode:",
|
||||
"VisualizationPanel.startLabel.text=Start:",
|
||||
"VisualizationPanel.endLabel.text=End:",
|
||||
"VisualizationPanel.countsToggle.text=Counts",
|
||||
"VisualizationPanel.detailsToggle.text=Details",
|
||||
"VisualizationPanel.zoomMenuButton.text=Zoom in/out to",
|
||||
"VisualizationPanel.tagsAddedOrDeleted=Tags have been created and/or deleted. The visualization may not be up to date."
|
||||
"ViewFrame.viewModeLabel.text=View Mode:",
|
||||
"ViewFrame.startLabel.text=Start:",
|
||||
"ViewFrame.endLabel.text=End:",
|
||||
"ViewFrame.countsToggle.text=Counts",
|
||||
"ViewFrame.detailsToggle.text=Details",
|
||||
"ViewFrame.listToggle.text=List",
|
||||
"ViewFrame.zoomMenuButton.text=Zoom in/out to",
|
||||
"ViewFrame.tagsAddedOrDeleted=Tags have been created and/or deleted. The view may not be up to date."
|
||||
})
|
||||
void initialize() {
|
||||
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||
@ -273,37 +278,32 @@ final public class VisualizationPanel extends BorderPane {
|
||||
notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
|
||||
setCenter(notificationPane);
|
||||
|
||||
//configure visualization mode toggle
|
||||
visualizationModeLabel.setText(Bundle.VisualizationPanel_visualizationModeLabel_text());
|
||||
countsToggle.setText(Bundle.VisualizationPanel_countsToggle_text());
|
||||
detailsToggle.setText(Bundle.VisualizationPanel_detailsToggle_text());
|
||||
ChangeListener<Toggle> toggleListener = (ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) -> {
|
||||
if (newValue == null) {
|
||||
countsToggle.getToggleGroup().selectToggle(oldValue != null ? oldValue : countsToggle);
|
||||
} else if (newValue == countsToggle && oldValue != null) {
|
||||
controller.setVisualizationMode(VisualizationMode.COUNTS);
|
||||
} else if (newValue == detailsToggle && oldValue != null) {
|
||||
controller.setVisualizationMode(VisualizationMode.DETAIL);
|
||||
}
|
||||
};
|
||||
//configure view mode toggle
|
||||
viewModeLabel.setText(Bundle.ViewFrame_viewModeLabel_text());
|
||||
countsToggle.setText(Bundle.ViewFrame_countsToggle_text());
|
||||
detailsToggle.setText(Bundle.ViewFrame_detailsToggle_text());
|
||||
listToggle.setText(Bundle.ViewFrame_listToggle_text());
|
||||
|
||||
if (countsToggle.getToggleGroup() != null) {
|
||||
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
|
||||
} else {
|
||||
countsToggle.toggleGroupProperty().addListener((Observable toggleGroup) -> {
|
||||
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
|
||||
});
|
||||
}
|
||||
ToggleGroupValue<ViewMode> visModeToggleGroup = new ToggleGroupValue<>();
|
||||
visModeToggleGroup.add(listToggle, ViewMode.LIST);
|
||||
visModeToggleGroup.add(detailsToggle, ViewMode.DETAIL);
|
||||
visModeToggleGroup.add(countsToggle, ViewMode.COUNTS);
|
||||
|
||||
controller.visualizationModeProperty().addListener(visualizationMode -> syncVisualizationMode());
|
||||
syncVisualizationMode();
|
||||
modeSegButton.setToggleGroup(visModeToggleGroup);
|
||||
|
||||
visModeToggleGroup.valueProperty().addListener((observable, oldVisMode, newValue) -> {
|
||||
controller.setViewMode(newValue != null ? newValue : (oldVisMode != null ? oldVisMode : ViewMode.COUNTS));
|
||||
});
|
||||
|
||||
controller.viewModeProperty().addListener(viewMode -> syncViewMode());
|
||||
syncViewMode();
|
||||
|
||||
ActionUtils.configureButton(new SaveSnapshotAsReport(controller, notificationPane::getContent), snapShotButton);
|
||||
ActionUtils.configureButton(new UpdateDB(controller), updateDBButton);
|
||||
|
||||
/////configure start and end pickers
|
||||
startLabel.setText(Bundle.VisualizationPanel_startLabel_text());
|
||||
endLabel.setText(Bundle.VisualizationPanel_endLabel_text());
|
||||
startLabel.setText(Bundle.ViewFrame_startLabel_text());
|
||||
endLabel.setText(Bundle.ViewFrame_endLabel_text());
|
||||
|
||||
//suppress stacktraces on malformed input
|
||||
//TODO: should we do anything else? show a warning?
|
||||
@ -326,7 +326,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
rangeHistogramStack.getChildren().add(rangeSlider);
|
||||
|
||||
/*
|
||||
* this padding attempts to compensates for the fact that the
|
||||
* This padding attempts to compensates for the fact that the
|
||||
* rangeslider track doesn't extend to edge of node,and so the
|
||||
* histrogram doesn't quite line up with the rangeslider
|
||||
*/
|
||||
@ -344,7 +344,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
}
|
||||
})));
|
||||
}
|
||||
zoomMenuButton.setText(Bundle.VisualizationPanel_zoomMenuButton_text());
|
||||
zoomMenuButton.setText(Bundle.ViewFrame_zoomMenuButton_text());
|
||||
ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
|
||||
ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
|
||||
|
||||
@ -355,28 +355,28 @@ final public class VisualizationPanel extends BorderPane {
|
||||
TimeLineController.getTimeZone().addListener(timeZoneProp -> refreshTimeUI());
|
||||
filteredEvents.timeRangeProperty().addListener(timeRangeProp -> refreshTimeUI());
|
||||
filteredEvents.zoomParametersProperty().addListener(zoomListener);
|
||||
refreshTimeUI(); //populate the viz
|
||||
refreshTimeUI(); //populate the view
|
||||
|
||||
refreshHistorgram();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle TagsUpdatedEvents by marking that the visualization needs to be
|
||||
* Handle TagsUpdatedEvents by marking that the view needs to be
|
||||
* refreshed.
|
||||
*
|
||||
* NOTE: This VisualizationPanel must be registered with the
|
||||
* NOTE: This ViewFrame must be registered with the
|
||||
* filteredEventsModel's EventBus in order for this handler to be invoked.
|
||||
*
|
||||
* @param event The TagsUpdatedEvent to handle.
|
||||
*/
|
||||
@Subscribe
|
||||
public void handleTimeLineTagUpdate(TagsUpdatedEvent event) {
|
||||
visualization.setOutOfDate();
|
||||
hostedView.setOutOfDate();
|
||||
Platform.runLater(() -> {
|
||||
if (notificationPane.isShowing() == false) {
|
||||
notificationPane.getActions().setAll(new Refresh());
|
||||
notificationPane.show(Bundle.VisualizationPanel_tagsAddedOrDeleted(), new ImageView(INFORMATION));
|
||||
notificationPane.show(Bundle.ViewFrame_tagsAddedOrDeleted(), new ImageView(INFORMATION));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -385,7 +385,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
* Handle a RefreshRequestedEvent from the events model by clearing the
|
||||
* refresh notification.
|
||||
*
|
||||
* NOTE: This VisualizationPanel must be registered with the
|
||||
* NOTE: This ViewFrame must be registered with the
|
||||
* filteredEventsModel's EventBus in order for this handler to be invoked.
|
||||
*
|
||||
* @param event The RefreshRequestedEvent to handle.
|
||||
@ -393,7 +393,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
@Subscribe
|
||||
public void handleRefreshRequested(RefreshRequestedEvent event) {
|
||||
Platform.runLater(() -> {
|
||||
if (Bundle.VisualizationPanel_tagsAddedOrDeleted().equals(notificationPane.getText())) {
|
||||
if (Bundle.ViewFrame_tagsAddedOrDeleted().equals(notificationPane.getText())) {
|
||||
notificationPane.hide();
|
||||
}
|
||||
});
|
||||
@ -401,16 +401,16 @@ final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
/**
|
||||
* Handle a DBUpdatedEvent from the events model by refreshing the
|
||||
* visualization.
|
||||
* view.
|
||||
*
|
||||
* NOTE: This VisualizationPanel must be registered with the
|
||||
* NOTE: This ViewFrame must be registered with the
|
||||
* filteredEventsModel's EventBus in order for this handler to be invoked.
|
||||
*
|
||||
* @param event The DBUpdatedEvent to handle.
|
||||
*/
|
||||
@Subscribe
|
||||
public void handleDBUpdated(DBUpdatedEvent event) {
|
||||
visualization.refresh();
|
||||
hostedView.refresh();
|
||||
refreshHistorgram();
|
||||
Platform.runLater(notificationPane::hide);
|
||||
}
|
||||
@ -419,7 +419,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
* Handle a DataSourceAddedEvent from the events model by showing a
|
||||
* notification.
|
||||
*
|
||||
* NOTE: This VisualizationPanel must be registered with the
|
||||
* NOTE: This ViewFrame must be registered with the
|
||||
* filteredEventsModel's EventBus in order for this handler to be invoked.
|
||||
*
|
||||
* @param event The DataSourceAddedEvent to handle.
|
||||
@ -427,11 +427,11 @@ final public class VisualizationPanel extends BorderPane {
|
||||
@Subscribe
|
||||
@NbBundle.Messages({
|
||||
"# {0} - datasource name",
|
||||
"VisualizationPanel.notification.newDataSource={0} has been added as a new datasource. The Timeline DB may be out of date."})
|
||||
"ViewFrame.notification.newDataSource={0} has been added as a new datasource. The Timeline DB may be out of date."})
|
||||
public void handlDataSourceAdded(DataSourceAddedEvent event) {
|
||||
Platform.runLater(() -> {
|
||||
notificationPane.getActions().setAll(new UpdateDB(controller));
|
||||
notificationPane.show(Bundle.VisualizationPanel_notification_newDataSource(event.getDataSource().getName()), new ImageView(WARNING));
|
||||
notificationPane.show(Bundle.ViewFrame_notification_newDataSource(event.getDataSource().getName()), new ImageView(WARNING));
|
||||
});
|
||||
}
|
||||
|
||||
@ -439,7 +439,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
* Handle a DataSourceAnalysisCompletedEvent from the events modelby showing
|
||||
* a notification.
|
||||
*
|
||||
* NOTE: This VisualizationPanel must be registered with the
|
||||
* NOTE: This ViewFrame must be registered with the
|
||||
* filteredEventsModel's EventBus in order for this handler to be invoked.
|
||||
*
|
||||
* @param event The DataSourceAnalysisCompletedEvent to handle.
|
||||
@ -447,11 +447,11 @@ final public class VisualizationPanel extends BorderPane {
|
||||
@Subscribe
|
||||
@NbBundle.Messages({
|
||||
"# {0} - datasource name",
|
||||
"VisualizationPanel.notification.analysisComplete=Analysis has finished for {0}. The Timeline DB may be out of date."})
|
||||
"ViewFrame.notification.analysisComplete=Analysis has finished for {0}. The Timeline DB may be out of date."})
|
||||
public void handleAnalysisCompleted(DataSourceAnalysisCompletedEvent event) {
|
||||
Platform.runLater(() -> {
|
||||
notificationPane.getActions().setAll(new UpdateDB(controller));
|
||||
notificationPane.show(Bundle.VisualizationPanel_notification_analysisComplete(event.getDataSource().getName()), new ImageView(WARNING));
|
||||
notificationPane.show(Bundle.ViewFrame_notification_analysisComplete(event.getDataSource().getName()), new ImageView(WARNING));
|
||||
});
|
||||
}
|
||||
|
||||
@ -464,13 +464,13 @@ final public class VisualizationPanel extends BorderPane {
|
||||
}
|
||||
|
||||
histogramTask = new LoggedTask<Void>(
|
||||
NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.title"), true) { // NON-NLS
|
||||
NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.title"), true) { // NON-NLS
|
||||
private final Lighting lighting = new Lighting();
|
||||
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
|
||||
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.preparing")); // NON-NLS
|
||||
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.preparing")); // NON-NLS
|
||||
|
||||
long max = 0;
|
||||
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval());
|
||||
@ -483,7 +483,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
//clear old data, and reset ranges and series
|
||||
Platform.runLater(() -> {
|
||||
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.resetUI")); // NON-NLS
|
||||
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.resetUI")); // NON-NLS
|
||||
|
||||
});
|
||||
|
||||
@ -500,7 +500,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
start = end;
|
||||
|
||||
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.queryDb")); // NON-NLS
|
||||
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.queryDb")); // NON-NLS
|
||||
//query for current range
|
||||
long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum();
|
||||
bins.add(count);
|
||||
@ -510,7 +510,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
final double fMax = Math.log(max);
|
||||
final ArrayList<Long> fbins = new ArrayList<>(bins);
|
||||
Platform.runLater(() -> {
|
||||
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.updateUI2")); // NON-NLS
|
||||
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.updateUI2")); // NON-NLS
|
||||
|
||||
histogramBox.getChildren().clear();
|
||||
|
||||
@ -576,18 +576,28 @@ final public class VisualizationPanel extends BorderPane {
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to the given VisualizationMode, by swapping out the hosted
|
||||
* AbstractVislualization for one of the correct type.
|
||||
* Switch to the given ViewMode, by swapping out the hosted
|
||||
* AbstractTimelineView for one of the correct type.
|
||||
*/
|
||||
private void syncVisualizationMode() {
|
||||
AbstractVisualizationPane<?, ?, ?, ?> vizPane;
|
||||
VisualizationMode visMode = controller.visualizationModeProperty().get();
|
||||
private void syncViewMode() {
|
||||
AbstractTimeLineView view;
|
||||
ViewMode viewMode = controller.viewModeProperty().get();
|
||||
|
||||
//make new visualization.
|
||||
switch (visMode) {
|
||||
//make new view.
|
||||
switch (viewMode) {
|
||||
case LIST:
|
||||
view = new ListViewPane(controller);
|
||||
Platform.runLater(() -> {
|
||||
listToggle.setSelected(true);
|
||||
//TODO: should remove listeners from events tree
|
||||
});
|
||||
break;
|
||||
case COUNTS:
|
||||
vizPane = new CountsViewPane(controller);
|
||||
Platform.runLater(() -> countsToggle.setSelected(true));
|
||||
view = new CountsViewPane(controller);
|
||||
Platform.runLater(() -> {
|
||||
countsToggle.setSelected(true);
|
||||
//TODO: should remove listeners from events tree
|
||||
});
|
||||
break;
|
||||
case DETAIL:
|
||||
DetailViewPane detailViewPane = new DetailViewPane(controller);
|
||||
@ -596,34 +606,34 @@ final public class VisualizationPanel extends BorderPane {
|
||||
detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents());
|
||||
eventsTree.setDetailViewPane(detailViewPane);
|
||||
});
|
||||
vizPane = detailViewPane;
|
||||
view = detailViewPane;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown VisualizationMode: " + visMode.toString());
|
||||
throw new IllegalArgumentException("Unknown ViewMode: " + viewMode.toString());
|
||||
}
|
||||
|
||||
//Set the new AbstractVisualizationPane as the one hosted by this VisualizationPanel.
|
||||
//Set the new AbstractTimeLineView as the one hosted by this ViewFrame.
|
||||
Platform.runLater(() -> {
|
||||
//clear out old vis.
|
||||
if (visualization != null) {
|
||||
toolBar.getItems().removeAll(visualization.getSettingsNodes());
|
||||
visualization.dispose();
|
||||
//clear out old view.
|
||||
if (hostedView != null) {
|
||||
toolBar.getItems().removeAll(hostedView.getSettingsNodes());
|
||||
hostedView.dispose();
|
||||
}
|
||||
|
||||
visualization = vizPane;
|
||||
//setup new vis.
|
||||
ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new visualization
|
||||
visualization.refresh();
|
||||
toolBar.getItems().addAll(2, vizPane.getSettingsNodes());
|
||||
notificationPane.setContent(visualization);
|
||||
hostedView = view;
|
||||
//setup new view.
|
||||
ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view
|
||||
hostedView.refresh();
|
||||
toolBar.getItems().addAll(2, view.getSettingsNodes());
|
||||
notificationPane.setContent(hostedView);
|
||||
|
||||
//listen to has events property and show "dialog" if it is false.
|
||||
visualization.hasVisibleEventsProperty().addListener(hasEvents -> {
|
||||
notificationPane.setContent(visualization.hasVisibleEvents()
|
||||
? visualization
|
||||
: new StackPane(visualization,
|
||||
hostedView.hasVisibleEventsProperty().addListener(hasEvents -> {
|
||||
notificationPane.setContent(hostedView.hasVisibleEvents()
|
||||
? hostedView
|
||||
: new StackPane(hostedView,
|
||||
NO_EVENTS_BACKGROUND,
|
||||
new NoEventsDialog(() -> notificationPane.setContent(visualization))
|
||||
new NoEventsDialog(() -> notificationPane.setContent(hostedView))
|
||||
)
|
||||
);
|
||||
});
|
||||
@ -660,7 +670,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
|
||||
|
||||
titledPane.setText(Bundle.NoEventsDialog_titledPane_text());
|
||||
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
|
||||
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "ViewFrame.noEventsDialogLabel.text")); // NON-NLS
|
||||
|
||||
dismissButton.setOnAction(actionEvent -> closeCallback.run());
|
||||
|
||||
@ -689,7 +699,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
LocalDateTime pickerTime = pickerSupplier.get().getLocalDateTime();
|
||||
if (pickerTime != null) {
|
||||
controller.pushTimeRange(intervalMapper.apply(filteredEvents.timeRangeProperty().get(), localDateTimeToEpochMilli(pickerTime)));
|
||||
Platform.runLater(VisualizationPanel.this::refreshTimeUI);
|
||||
Platform.runLater(ViewFrame.this::refreshTimeUI);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -757,19 +767,19 @@ final public class VisualizationPanel extends BorderPane {
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that refreshes the Visualization.
|
||||
* Action that refreshes the View.
|
||||
*/
|
||||
private class Refresh extends Action {
|
||||
|
||||
@NbBundle.Messages({
|
||||
"VisualizationPanel.refresh.text=Refresh Vis.",
|
||||
"VisualizationPanel.refresh.longText=Refresh the visualization to include information that is in the DB but not visualized, such as newly updated tags."})
|
||||
"ViewFrame.refresh.text=Refresh View",
|
||||
"ViewFrame.refresh.longText=Refresh the view to include information that is in the DB but not displayed, such as newly updated tags."})
|
||||
Refresh() {
|
||||
super(Bundle.VisualizationPanel_refresh_text());
|
||||
setLongText(Bundle.VisualizationPanel_refresh_longText());
|
||||
super(Bundle.ViewFrame_refresh_text());
|
||||
setLongText(Bundle.ViewFrame_refresh_longText());
|
||||
setGraphic(new ImageView(REFRESH));
|
||||
setEventHandler(actionEvent -> filteredEvents.postRefreshRequest());
|
||||
disabledProperty().bind(visualization.outOfDateProperty().not());
|
||||
disabledProperty().bind(hostedView.outOfDateProperty().not());
|
||||
}
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
/**
|
||||
@ -77,7 +77,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
* Platform.runLater(java.lang.Runnable). The FilteredEventsModel should
|
||||
* encapsulate all need synchronization internally.
|
||||
*/
|
||||
public class CountsViewPane extends AbstractVisualizationPane<String, Number, Node, EventCountsChart> {
|
||||
public class CountsViewPane extends AbstractTimelineChart<String, Number, Node, EventCountsChart> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CountsViewPane.class.getName());
|
||||
|
||||
@ -105,13 +105,15 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The TimelineController for this visualization.
|
||||
* @param controller The TimelineController for this view.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - scale name",
|
||||
"CountsViewPane.numberOfEvents=Number of Events ({0})"})
|
||||
public CountsViewPane(TimeLineController controller) {
|
||||
super(controller);
|
||||
|
||||
|
||||
setChart(new EventCountsChart(controller, dateAxis, countAxis, getSelectedNodes()));
|
||||
getChart().setData(dataSeries);
|
||||
Tooltip.install(getChart(), getDefaultTooltip());
|
||||
@ -156,7 +158,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
@Override
|
||||
protected void clearChartData() {
|
||||
protected void clearData() {
|
||||
for (XYChart.Series<String, Number> series : dataSeries) {
|
||||
series.getData().clear();
|
||||
}
|
||||
@ -247,7 +249,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
"CountsViewPane.scaleLabel.text=Scale:",
|
||||
"CountsViewPane.scaleHelp.label.text=Scales: ",
|
||||
"CountsViewPane.linearRadio.text=Linear",
|
||||
"CountsViewPane.scaleHelpLinear=The linear scale is good for many use cases. When this scale is selected, the height of the bars represents the counts in a linear, one-to-one fashion, and the y-axis is labeled with values. When the range of values is very large, time periods with low counts may have a bar that is too small to see. To help the user detect this, the labels for date ranges with events are bold. To see bars that are too small, there are three options: adjust the window size so that the visualization area has more vertical space, adjust the time range shown so that time periods with larger bars are excluded, or adjust the scale setting to logarithmic.",
|
||||
"CountsViewPane.scaleHelpLinear=The linear scale is good for many use cases. When this scale is selected, the height of the bars represents the counts in a linear, one-to-one fashion, and the y-axis is labeled with values. When the range of values is very large, time periods with low counts may have a bar that is too small to see. To help the user detect this, the labels for date ranges with events are bold. To see bars that are too small, there are three options: adjust the window size so that the timeline has more vertical space, adjust the time range shown so that time periods with larger bars are excluded, or adjust the scale setting to logarithmic.",
|
||||
"CountsViewPane.scaleHelpLog=The logarithmic scale represents the number of events in a non-linear way that compresses the difference between large and small numbers. Note that even with the logarithmic scale, an extremely large difference in counts may still produce bars too small to see. In this case the only option may be to filter events to reduce the difference in counts. NOTE: Because the logarithmic scale is applied to each event type separately, the meaning of the height of the combined bar is not intuitive, and to emphasize this, no labels are shown on the y-axis with the logarithmic scale. The logarithmic scale should be used to quickly compare the counts ",
|
||||
"CountsViewPane.scaleHelpLog2=across time within a type, or across types for one time period, but not both.",
|
||||
"CountsViewPane.scaleHelpLog3= The actual counts (available in tooltips or the result viewer) should be used for absolute comparisons. Use the logarithmic scale with care."})
|
||||
@ -334,13 +336,19 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"CountsViewPane.loggedTask.name=Updating Counts View",
|
||||
"CountsViewPane.loggedTask.updatingCounts=Populating visualization"})
|
||||
private class CountsUpdateTask extends VisualizationRefreshTask<List<String>> {
|
||||
"CountsViewPane.loggedTask.updatingCounts=Populating view"})
|
||||
private class CountsUpdateTask extends ViewRefreshTask<List<String>> {
|
||||
|
||||
CountsUpdateTask() {
|
||||
super(Bundle.CountsViewPane_loggedTask_name(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
layoutDateLabels();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean call() throws Exception {
|
||||
super.call();
|
||||
@ -354,7 +362,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
List<Interval> intervals = rangeInfo.getIntervals();
|
||||
|
||||
//clear old data, and reset ranges and series
|
||||
resetChart(Lists.transform(intervals, rangeInfo::formatForTick));
|
||||
resetView(Lists.transform(intervals, rangeInfo::formatForTick));
|
||||
|
||||
updateMessage(Bundle.CountsViewPane_loggedTask_updatingCounts());
|
||||
int chartMax = 0;
|
||||
@ -412,7 +420,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDateAxisValues(List<String> categories) {
|
||||
protected void setDateValues(List<String> categories) {
|
||||
dateAxis.getCategories().setAll(categories);
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ import org.joda.time.Seconds;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.ColorUtilities;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.ViewMode;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||
@ -398,27 +398,8 @@ final class EventCountsChart extends StackedBarChart<String, Number> implements
|
||||
Bundle.CountsViewPane_detailSwitchMessage(),
|
||||
Bundle.CountsViewPane_detailSwitchTitle(), JOptionPane.YES_NO_OPTION);
|
||||
if (showConfirmDialog == JOptionPane.YES_OPTION) {
|
||||
controller.setVisualizationMode(VisualizationMode.DETAIL);
|
||||
controller.setViewMode(ViewMode.DETAIL);
|
||||
}
|
||||
|
||||
/*
|
||||
* //I would like to use the JAvafx dialog, but it doesn't
|
||||
* block the ui (because it is embeded in a TopComponent)
|
||||
* -jm
|
||||
*
|
||||
* final Dialogs.CommandLink yes = new
|
||||
* Dialogs.CommandLink("Yes", "switch to Details view");
|
||||
* final Dialogs.CommandLink no = new
|
||||
* Dialogs.CommandLink("No", "return to Counts view with a
|
||||
* resolution of Seconds"); Action choice = Dialogs.create()
|
||||
* .title("Switch to Details View?") .masthead("There is no
|
||||
* temporal resolution smaller than Seconds.")
|
||||
* .message("Would you like to switch to the Details view
|
||||
* instead?") .showCommandLinks(Arrays.asList(yes, no));
|
||||
*
|
||||
* if (choice == yes) {
|
||||
* controller.setViewMode(VisualizationMode.DETAIL); }
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
|
||||
import org.sleuthkit.autopsy.timeline.utils.MappedList;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
@ -73,7 +73,7 @@ import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
* grouped EventStripes, etc, etc. The leaves of the trees are EventClusters or
|
||||
* SingleEvents.
|
||||
*/
|
||||
public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStripe, EventNodeBase<?>, DetailsChart> {
|
||||
public class DetailViewPane extends AbstractTimelineChart<DateTime, EventStripe, EventNodeBase<?>, DetailsChart> {
|
||||
|
||||
private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
|
||||
|
||||
@ -91,7 +91,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
|
||||
|
||||
/**
|
||||
* Local copy of the zoomParams. Used to backout of a zoomParam change
|
||||
* without needing to requery/redraw the vis.
|
||||
* without needing to requery/redraw the view.
|
||||
*/
|
||||
private ZoomParams currentZoomParams;
|
||||
|
||||
@ -204,7 +204,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
@Override
|
||||
protected void clearChartData() {
|
||||
protected void clearData() {
|
||||
getChart().reset();
|
||||
}
|
||||
|
||||
@ -347,13 +347,12 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
|
||||
@NbBundle.Messages({
|
||||
"DetailViewPane.loggedTask.queryDb=Retreiving event data",
|
||||
"DetailViewPane.loggedTask.name=Updating Details View",
|
||||
"DetailViewPane.loggedTask.updateUI=Populating visualization",
|
||||
"DetailViewPane.loggedTask.updateUI=Populating view",
|
||||
"DetailViewPane.loggedTask.continueButton=Continue",
|
||||
"DetailViewPane.loggedTask.backButton=Back (Cancel)",
|
||||
"# {0} - number of events",
|
||||
|
||||
"DetailViewPane.loggedTask.prompt=You are about to show details for {0} events. This might be very slow and could exhaust available memory.\n\nDo you want to continue?"})
|
||||
private class DetailsUpdateTask extends VisualizationRefreshTask<Interval> {
|
||||
private class DetailsUpdateTask extends ViewRefreshTask<Interval> {
|
||||
|
||||
DetailsUpdateTask() {
|
||||
super(Bundle.DetailViewPane_loggedTask_name(), true);
|
||||
@ -409,7 +408,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
|
||||
currentZoomParams = newZoomParams;
|
||||
|
||||
//clear the chart and set the horixontal axis
|
||||
resetChart(eventsModel.getTimeRange());
|
||||
resetView(eventsModel.getTimeRange());
|
||||
|
||||
updateMessage(Bundle.DetailViewPane_loggedTask_updateUI());
|
||||
|
||||
@ -433,9 +432,15 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDateAxisValues(Interval timeRange) {
|
||||
protected void setDateValues(Interval timeRange) {
|
||||
detailsChartDateAxis.setRange(timeRange, true);
|
||||
pinnedDateAxis.setRange(timeRange, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
layoutDateLabels();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -432,8 +432,8 @@ final class DetailsChart extends Control implements TimeLineChart<DateTime> {
|
||||
configureMouseListeners(pinnedLane, mouseClickedHandler, chartDragHandler);
|
||||
|
||||
//show and hide pinned lane in response to settings property change
|
||||
getSkinnable().getLayoutSettings().pinnedLaneShowing().addListener(observable -> syncPinnedShowing());
|
||||
syncPinnedShowing();
|
||||
getSkinnable().getLayoutSettings().pinnedLaneShowing().addListener(observable -> syncPinnedLaneShowing());
|
||||
syncPinnedLaneShowing();
|
||||
|
||||
//show and remove interval selector in sync with control state change
|
||||
getSkinnable().intervalSelectorProp.addListener((observable, oldIntervalSelector, newIntervalSelector) -> {
|
||||
@ -484,7 +484,7 @@ final class DetailsChart extends Control implements TimeLineChart<DateTime> {
|
||||
* Show the pinned lane if and only if the settings object says it
|
||||
* should be.
|
||||
*/
|
||||
private void syncPinnedShowing() {
|
||||
private void syncPinnedLaneShowing() {
|
||||
boolean pinnedLaneShowing = getSkinnable().getLayoutSettings().isPinnedLaneShowing();
|
||||
if (pinnedLaneShowing == false) {
|
||||
//Save the divider position for later.
|
||||
|
@ -58,11 +58,11 @@ import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
|
||||
import org.sleuthkit.autopsy.timeline.ui.ContextMenuProvider;
|
||||
|
||||
/**
|
||||
* One "lane" of a the details visualization, contains all the core logic and
|
||||
* One "lane" of a the details view, contains all the core logic and
|
||||
* layout code.
|
||||
*
|
||||
* NOTE: It was too hard to control the threading of this chart via the
|
||||
@ -178,7 +178,7 @@ abstract class DetailsChartLane<Y extends TimeLineEvent> extends XYChart<DateTim
|
||||
//add a dummy series or the chart is never rendered
|
||||
setData(FXCollections.observableList(Arrays.asList(new Series<DateTime, Y>())));
|
||||
|
||||
Tooltip.install(this, AbstractVisualizationPane.getDefaultTooltip());
|
||||
Tooltip.install(this, AbstractTimelineChart.getDefaultTooltip());
|
||||
|
||||
dateAxis.setAutoRanging(false);
|
||||
setLegendVisible(false);
|
||||
|
@ -77,7 +77,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.events.TagsAddedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.events.TagsDeletedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
|
||||
import org.sleuthkit.autopsy.timeline.ui.ContextMenuProvider;
|
||||
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventNodeBase.show;
|
||||
import static org.sleuthkit.autopsy.timeline.ui.detailview.MultiEventNodeBase.CORNER_RADII_3;
|
||||
@ -167,7 +167,7 @@ public abstract class EventNodeBase<Type extends TimeLineEvent> extends StackPan
|
||||
|
||||
//set up mouse hover effect and tooltip
|
||||
setOnMouseEntered(mouseEntered -> {
|
||||
Tooltip.uninstall(chartLane, AbstractVisualizationPane.getDefaultTooltip());
|
||||
Tooltip.uninstall(chartLane, AbstractTimelineChart.getDefaultTooltip());
|
||||
showHoverControls(true);
|
||||
toFront();
|
||||
});
|
||||
@ -176,7 +176,7 @@ public abstract class EventNodeBase<Type extends TimeLineEvent> extends StackPan
|
||||
if (parentNode != null) {
|
||||
parentNode.showHoverControls(true);
|
||||
} else {
|
||||
Tooltip.install(chartLane, AbstractVisualizationPane.getDefaultTooltip());
|
||||
Tooltip.install(chartLane, AbstractTimelineChart.getDefaultTooltip());
|
||||
}
|
||||
});
|
||||
setOnMouseClicked(new ClickHandler());
|
||||
|
@ -25,7 +25,7 @@ import javafx.scene.shape.Line;
|
||||
import org.joda.time.DateTime;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
|
||||
|
||||
/**
|
||||
* Subclass of {@link Line} with appropriate behavior (mouse listeners) to act
|
||||
@ -35,7 +35,7 @@ import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
"GuideLine.tooltip.text={0}\nRight-click to remove.\nDrag to reposition."})
|
||||
class GuideLine extends Line {
|
||||
|
||||
private static final Tooltip CHART_DEFAULT_TOOLTIP = AbstractVisualizationPane.getDefaultTooltip();
|
||||
private final Tooltip CHART_DEFAULT_TOOLTIP = AbstractTimelineChart.getDefaultTooltip();
|
||||
|
||||
private final Tooltip tooltip = new Tooltip();
|
||||
private final DetailsChart chart;
|
||||
|
@ -41,7 +41,6 @@ import org.controlsfx.control.action.ActionUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||
@ -129,20 +128,29 @@ final public class FilterSetPanel extends BorderPane {
|
||||
hiddenDescriptionsListView.setItems(controller.getQuickHideFilters());
|
||||
hiddenDescriptionsListView.setCellFactory(listView -> getNewDiscriptionFilterListCell());
|
||||
|
||||
controller.visualizationModeProperty().addListener(observable -> {
|
||||
//show and hide the "hidden descriptions" panel depending on the current view mode
|
||||
controller.viewModeProperty().addListener(observable -> {
|
||||
applyFilters();
|
||||
if (controller.visualizationModeProperty().get() == VisualizationMode.COUNTS) {
|
||||
dividerPosition = splitPane.getDividerPositions()[0];
|
||||
splitPane.setDividerPositions(1);
|
||||
hiddenDescriptionsPane.setExpanded(false);
|
||||
hiddenDescriptionsPane.setCollapsible(false);
|
||||
hiddenDescriptionsPane.setDisable(true);
|
||||
} else {
|
||||
splitPane.setDividerPositions(dividerPosition);
|
||||
hiddenDescriptionsPane.setDisable(false);
|
||||
hiddenDescriptionsPane.setCollapsible(true);
|
||||
hiddenDescriptionsPane.setExpanded(true);
|
||||
hiddenDescriptionsPane.setCollapsible(false);
|
||||
switch (controller.getViewMode()) {
|
||||
case COUNTS:
|
||||
case LIST:
|
||||
//hide for counts and lists, but remember divider position
|
||||
dividerPosition = splitPane.getDividerPositions()[0];
|
||||
splitPane.setDividerPositions(1);
|
||||
hiddenDescriptionsPane.setExpanded(false);
|
||||
hiddenDescriptionsPane.setCollapsible(false);
|
||||
hiddenDescriptionsPane.setDisable(true);
|
||||
break;
|
||||
case DETAIL:
|
||||
//show and restore divider position.
|
||||
splitPane.setDividerPositions(dividerPosition);
|
||||
hiddenDescriptionsPane.setDisable(false);
|
||||
hiddenDescriptionsPane.setCollapsible(true);
|
||||
hiddenDescriptionsPane.setExpanded(true);
|
||||
hiddenDescriptionsPane.setCollapsible(false);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
|
||||
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<top>
|
||||
<HBox alignment="CENTER" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
<Label fx:id="eventCountLabel" text=" # of events" />
|
||||
</children>
|
||||
<BorderPane.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</BorderPane.margin>
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<TableView fx:id="table" tableMenuButtonVisible="true" BorderPane.alignment="CENTER">
|
||||
<columns>
|
||||
<TableColumn fx:id="dateTimeColumn" editable="false" maxWidth="200.0" minWidth="150.0" prefWidth="150.0" resizable="false" sortable="false" text="Date/Time" />
|
||||
<TableColumn fx:id="typeColumn" editable="false" maxWidth="100.0" minWidth="100.0" prefWidth="100.0" sortable="false" text="Event Type" />
|
||||
<TableColumn fx:id="descriptionColumn" editable="false" maxWidth="3000.0" minWidth="100.0" prefWidth="300.0" sortable="false" text="Description" />
|
||||
<TableColumn fx:id="knownColumn" editable="false" maxWidth="75.0" minWidth="75.0" prefWidth="75.0" resizable="false" sortable="false" text="Known" />
|
||||
<TableColumn fx:id="idColumn" editable="false" maxWidth="50.0" minWidth="50.0" prefWidth="50.0" resizable="false" sortable="false" text="ID" />
|
||||
<TableColumn fx:id="taggedColumn" maxWidth="75.0" minWidth="75.0" prefWidth="75.0" resizable="false" text="Tagged" />
|
||||
<TableColumn fx:id="hashHitColumn" maxWidth="75.0" minWidth="75.0" prefWidth="75.0" resizable="false" text="Hash Hit" />
|
||||
</columns>
|
||||
<columnResizePolicy>
|
||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
|
||||
</columnResizePolicy>
|
||||
</TableView>
|
||||
</center>
|
||||
</fx:root>
|
@ -0,0 +1,575 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-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.timeline.ui.listvew;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.OverrunStyle;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableRow;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.JMenuItem;
|
||||
import org.controlsfx.control.Notifications;
|
||||
import org.openide.awt.Actions;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.actions.Presenter;
|
||||
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.BaseTypes;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.FileSystemTypes;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventNode;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* The inner component that makes up the List view. Manages the TableView.
|
||||
*/
|
||||
class ListTimeline extends BorderPane {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(ListTimeline.class.getName());
|
||||
|
||||
private static final Image HASH_HIT = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); //NON-NLS
|
||||
private static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); //NON-NLS
|
||||
|
||||
/**
|
||||
* call-back used to wrap the CombinedEvent in a ObservableValue
|
||||
*/
|
||||
private static final Callback<TableColumn.CellDataFeatures<CombinedEvent, CombinedEvent>, ObservableValue<CombinedEvent>> CELL_VALUE_FACTORY = param -> new SimpleObjectProperty<>(param.getValue());
|
||||
|
||||
@FXML
|
||||
private Label eventCountLabel;
|
||||
@FXML
|
||||
private TableView<CombinedEvent> table;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> idColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> dateTimeColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> descriptionColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> typeColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> knownColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> taggedColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> hashHitColumn;
|
||||
|
||||
/**
|
||||
* Observable list used to track selected events.
|
||||
*/
|
||||
private final ObservableList<Long> selectedEventIDs = FXCollections.observableArrayList();
|
||||
|
||||
private final TimeLineController controller;
|
||||
private final SleuthkitCase sleuthkitCase;
|
||||
private final TagsManager tagsManager;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The controller for this timeline
|
||||
*/
|
||||
ListTimeline(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
sleuthkitCase = controller.getAutopsyCase().getSleuthkitCase();
|
||||
tagsManager = controller.getAutopsyCase().getServices().getTagsManager();
|
||||
FXMLConstructor.construct(this, ListTimeline.class, "ListTimeline.fxml"); //NON-NLS
|
||||
}
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({
|
||||
"# {0} - the number of events",
|
||||
"ListTimeline.eventCountLabel.text={0} events"})
|
||||
void initialize() {
|
||||
assert eventCountLabel != null : "fx:id=\"eventCountLabel\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
assert table != null : "fx:id=\"table\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
assert idColumn != null : "fx:id=\"idColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
assert dateTimeColumn != null : "fx:id=\"dateTimeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
assert descriptionColumn != null : "fx:id=\"descriptionColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
assert typeColumn != null : "fx:id=\"typeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
assert knownColumn != null : "fx:id=\"knownColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
|
||||
//override default row with one that provides context menus
|
||||
table.setRowFactory(tableView -> new EventRow());
|
||||
|
||||
//remove idColumn (can be restored for debugging).
|
||||
table.getColumns().remove(idColumn);
|
||||
|
||||
//// set up cell and cell-value factories for columns
|
||||
dateTimeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
dateTimeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
|
||||
TimeLineController.getZonedFormatter().print(singleEvent.getStartMillis())));
|
||||
|
||||
descriptionColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
descriptionColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
|
||||
singleEvent.getDescription(DescriptionLoD.FULL)));
|
||||
|
||||
typeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
typeColumn.setCellFactory(col -> new EventTypeCell());
|
||||
|
||||
knownColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
knownColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
|
||||
singleEvent.getKnown().getName()));
|
||||
|
||||
taggedColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
taggedColumn.setCellFactory(col -> new TaggedCell());
|
||||
|
||||
hashHitColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
hashHitColumn.setCellFactory(col -> new HashHitCell());
|
||||
|
||||
//bind event count label to number of items in the table
|
||||
eventCountLabel.textProperty().bind(new StringBinding() {
|
||||
{
|
||||
bind(table.getItems());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String computeValue() {
|
||||
return Bundle.ListTimeline_eventCountLabel_text(table.getItems().size());
|
||||
}
|
||||
});
|
||||
|
||||
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
table.getSelectionModel().getSelectedItems().addListener((Observable observable) -> {
|
||||
//keep the selectedEventsIDs in sync with the table's selection model, via getRepresentitiveEventID().
|
||||
selectedEventIDs.setAll(table.getSelectionModel().getSelectedItems().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(CombinedEvent::getRepresentativeEventID)
|
||||
.collect(Collectors.toSet()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the events out of the table.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
void clear() {
|
||||
table.getItems().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Collection of CombinedEvents to show in the table.
|
||||
*
|
||||
* @param events The Collection of events to sho in the table.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
void setCombinedEvents(Collection<CombinedEvent> events) {
|
||||
table.getItems().setAll(events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an ObservableList of IDs of events that are selected in this table.
|
||||
*
|
||||
* @return An ObservableList of IDs of events that are selected in this
|
||||
* table.
|
||||
*/
|
||||
ObservableList<Long> getSelectedEventIDs() {
|
||||
return selectedEventIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an ObservableList of combined events that are selected in this table.
|
||||
*
|
||||
* @return An ObservableList of combined events that are selected in this
|
||||
* table.
|
||||
*/
|
||||
ObservableList<CombinedEvent> getSelectedEvents() {
|
||||
return table.getSelectionModel().getSelectedItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the combined events that are selected in this view.
|
||||
*
|
||||
* @param selectedEvents The events that should be selected.
|
||||
*/
|
||||
void selectEvents(Collection<CombinedEvent> selectedEvents) {
|
||||
CombinedEvent firstSelected = selectedEvents.stream().min(Comparator.comparing(CombinedEvent::getStartMillis)).orElse(null);
|
||||
table.getSelectionModel().clearSelection();
|
||||
table.scrollTo(firstSelected);
|
||||
selectedEvents.forEach(table.getSelectionModel()::select);
|
||||
table.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* TableCell to show the (sub) type of an event.
|
||||
*/
|
||||
private class EventTypeCell extends EventTableCell {
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ListView.EventTypeCell.modifiedTooltip=File Modified ( M )",
|
||||
"ListView.EventTypeCell.accessedTooltip=File Accessed ( A )",
|
||||
"ListView.EventTypeCell.createdTooltip=File Created ( B, for Born )",
|
||||
"ListView.EventTypeCell.changedTooltip=File Changed ( C )"
|
||||
})
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
setTooltip(null);
|
||||
} else {
|
||||
if (item.getEventTypes().stream().allMatch(eventType -> eventType instanceof FileSystemTypes)) {
|
||||
String typeString = ""; //NON-NLS
|
||||
VBox toolTipVbox = new VBox(5);
|
||||
|
||||
for (FileSystemTypes type : Arrays.asList(FileSystemTypes.FILE_MODIFIED, FileSystemTypes.FILE_ACCESSED, FileSystemTypes.FILE_CHANGED, FileSystemTypes.FILE_CREATED)) {
|
||||
if (item.getEventTypes().contains(type)) {
|
||||
switch (type) {
|
||||
case FILE_MODIFIED:
|
||||
typeString += "M"; //NON-NLS
|
||||
toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_modifiedTooltip(), new ImageView(type.getFXImage())));
|
||||
break;
|
||||
case FILE_ACCESSED:
|
||||
typeString += "A"; //NON-NLS
|
||||
toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_accessedTooltip(), new ImageView(type.getFXImage())));
|
||||
break;
|
||||
case FILE_CREATED:
|
||||
typeString += "B"; //NON-NLS
|
||||
toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_createdTooltip(), new ImageView(type.getFXImage())));
|
||||
break;
|
||||
case FILE_CHANGED:
|
||||
typeString += "C"; //NON-NLS
|
||||
toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_changedTooltip(), new ImageView(type.getFXImage())));
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown FileSystemType: " + type.name()); //NON-NLS
|
||||
}
|
||||
} else {
|
||||
typeString += "_"; //NON-NLS
|
||||
}
|
||||
}
|
||||
setText(typeString);
|
||||
setGraphic(new ImageView(BaseTypes.FILE_SYSTEM.getFXImage()));
|
||||
Tooltip tooltip = new Tooltip();
|
||||
tooltip.setGraphic(toolTipVbox);
|
||||
setTooltip(tooltip);
|
||||
|
||||
} else {
|
||||
EventType eventType = Iterables.getOnlyElement(item.getEventTypes());
|
||||
setText(eventType.getDisplayName());
|
||||
setGraphic(new ImageView(eventType.getFXImage()));
|
||||
setTooltip(new Tooltip(eventType.getDisplayName()));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A TableCell that shows information about the tags applied to a event.
|
||||
*/
|
||||
private class TaggedCell extends EventTableCell {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
TaggedCell() {
|
||||
setAlignment(Pos.CENTER);
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ListTimeline.taggedTooltip.error=There was a problem getting the tag names for the selected event.",
|
||||
"# {0} - tag names",
|
||||
"ListTimeline.taggedTooltip.text=Tags:\n{0}"})
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null || (getEvent().isTagged() == false)) {
|
||||
setGraphic(null);
|
||||
setTooltip(null);
|
||||
} else {
|
||||
/*
|
||||
* if the cell is not empty and the event is tagged, show the
|
||||
* tagged icon, and show a list of tag names in the tooltip
|
||||
*/
|
||||
setGraphic(new ImageView(TAG));
|
||||
|
||||
SortedSet<String> tagNames = new TreeSet<>();
|
||||
try {
|
||||
//get file tags
|
||||
AbstractFile abstractFileById = sleuthkitCase.getAbstractFileById(getEvent().getFileID());
|
||||
tagsManager.getContentTagsByContent(abstractFileById).stream()
|
||||
.map(tag -> tag.getName().getDisplayName())
|
||||
.forEach(tagNames::add);
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup tags for obj id " + getEvent().getFileID(), ex); //NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
Notifications.create()
|
||||
.owner(getScene().getWindow())
|
||||
.text(Bundle.ListTimeline_taggedTooltip_error())
|
||||
.showError();
|
||||
});
|
||||
}
|
||||
getEvent().getArtifactID().ifPresent(artifactID -> {
|
||||
//get artifact tags, if there is an artifact associated with the event.
|
||||
try {
|
||||
BlackboardArtifact artifact = sleuthkitCase.getBlackboardArtifact(artifactID);
|
||||
tagsManager.getBlackboardArtifactTagsByArtifact(artifact).stream()
|
||||
.map(tag -> tag.getName().getDisplayName())
|
||||
.forEach(tagNames::add);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup tags for artifact id " + artifactID, ex); //NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
Notifications.create()
|
||||
.owner(getScene().getWindow())
|
||||
.text(Bundle.ListTimeline_taggedTooltip_error())
|
||||
.showError();
|
||||
});
|
||||
}
|
||||
});
|
||||
Tooltip tooltip = new Tooltip(Bundle.ListTimeline_taggedTooltip_text(String.join("\n", tagNames))); //NON-NLS
|
||||
tooltip.setGraphic(new ImageView(TAG));
|
||||
setTooltip(tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TableCell to show the hash hits if any associated with the file backing
|
||||
* an event.
|
||||
*/
|
||||
private class HashHitCell extends EventTableCell {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
HashHitCell() {
|
||||
setAlignment(Pos.CENTER);
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ListTimeline.hashHitTooltip.error=There was a problem getting the hash set names for the selected event.",
|
||||
"# {0} - hash set names",
|
||||
"ListTimeline.hashHitTooltip.text=Hash Sets:\n{0}"})
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null || (getEvent().isHashHit() == false)) {
|
||||
setGraphic(null);
|
||||
setTooltip(null);
|
||||
} else {
|
||||
/*
|
||||
* if the cell is not empty and the event's file is a hash hit,
|
||||
* show the hash hit icon, and show a list of hash set names in
|
||||
* the tooltip
|
||||
*/
|
||||
setGraphic(new ImageView(HASH_HIT));
|
||||
try {
|
||||
Set<String> hashSetNames = new TreeSet<>(sleuthkitCase.getAbstractFileById(getEvent().getFileID()).getHashSetNames());
|
||||
Tooltip tooltip = new Tooltip(Bundle.ListTimeline_hashHitTooltip_text(String.join("\n", hashSetNames))); //NON-NLS
|
||||
tooltip.setGraphic(new ImageView(HASH_HIT));
|
||||
setTooltip(tooltip);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup hash set names for obj id " + getEvent().getFileID(), ex); //NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
Notifications.create()
|
||||
.owner(getScene().getWindow())
|
||||
.text(Bundle.ListTimeline_hashHitTooltip_error())
|
||||
.showError();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TableCell to show text derived from a SingleEvent by the given Function.
|
||||
*/
|
||||
private class TextEventTableCell extends EventTableCell {
|
||||
|
||||
private final Function<SingleEvent, String> textSupplier;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param textSupplier Function that takes a SingleEvent and produces a
|
||||
* String to show in this TableCell.
|
||||
*/
|
||||
TextEventTableCell(Function<SingleEvent, String> textSupplier) {
|
||||
this.textSupplier = textSupplier;
|
||||
setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
|
||||
setEllipsisString(" ... "); //NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(textSupplier.apply(getEvent()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for TableCells that represent a MergedEvent by way of a
|
||||
* representative SingleEvent.
|
||||
*/
|
||||
private abstract class EventTableCell extends TableCell<CombinedEvent, CombinedEvent> {
|
||||
|
||||
private SingleEvent event;
|
||||
|
||||
/**
|
||||
* Get the representative SingleEvent for this cell.
|
||||
*
|
||||
* @return The representative SingleEvent for this cell.
|
||||
*/
|
||||
SingleEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
event = null;
|
||||
} else {
|
||||
//stash the event in the cell for derived classed to use.
|
||||
event = controller.getEventsModel().getEventById(item.getRepresentativeEventID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TableRow that adds a right-click context menu.
|
||||
*/
|
||||
private class EventRow extends TableRow<CombinedEvent> {
|
||||
|
||||
private SingleEvent event;
|
||||
|
||||
/**
|
||||
* Get the representative SingleEvent for this row .
|
||||
*
|
||||
* @return The representative SingleEvent for this row .
|
||||
*/
|
||||
SingleEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ListChart.errorMsg=There was a problem getting the content for the selected event."})
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
event = null;
|
||||
} else {
|
||||
event = controller.getEventsModel().getEventById(item.getRepresentativeEventID());
|
||||
|
||||
setOnContextMenuRequested(contextMenuEvent -> {
|
||||
//make a new context menu on each request in order to include uptodate tag names and hash sets
|
||||
try {
|
||||
EventNode node = EventNode.createEventNode(item.getRepresentativeEventID(), controller.getEventsModel());
|
||||
List<MenuItem> menuItems = new ArrayList<>();
|
||||
|
||||
//for each actions avaialable on node, make a menu item.
|
||||
for (Action action : node.getActions(false)) {
|
||||
if (action == null) {
|
||||
// swing/netbeans uses null action to represent separator in menu
|
||||
menuItems.add(new SeparatorMenuItem());
|
||||
} else {
|
||||
String actionName = Objects.toString(action.getValue(Action.NAME));
|
||||
//for now, suppress properties and tools actions, by ignoring them
|
||||
if (Arrays.asList("&Properties", "Tools").contains(actionName) == false) { //NON-NLS
|
||||
if (action instanceof Presenter.Popup) {
|
||||
/*
|
||||
* If the action is really the root of a
|
||||
* set of actions (eg, tagging). Make a
|
||||
* menu that parallels the action's
|
||||
* menu.
|
||||
*/
|
||||
JMenuItem submenu = ((Presenter.Popup) action).getPopupPresenter();
|
||||
menuItems.add(SwingFXMenuUtils.createFXMenu(submenu));
|
||||
} else {
|
||||
menuItems.add(SwingFXMenuUtils.createFXMenu(new Actions.MenuItem(action, false)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//show new context menu.
|
||||
new ContextMenu(menuItems.toArray(new MenuItem[menuItems.size()]))
|
||||
.show(this, contextMenuEvent.getScreenX(), contextMenuEvent.getScreenY());
|
||||
} catch (IllegalStateException ex) {
|
||||
//Since the case is closed, the user probably doesn't care about this, just log it as a precaution.
|
||||
LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); //NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
Notifications.create()
|
||||
.owner(getScene().getWindow())
|
||||
.text(Bundle.ListChart_errorMsg())
|
||||
.showError();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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.timeline.ui.listvew;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.scene.Parent;
|
||||
import org.joda.time.Interval;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView;
|
||||
|
||||
/**
|
||||
* An AbstractTimeLineView that uses a TableView to display events.
|
||||
*/
|
||||
public class ListViewPane extends AbstractTimeLineView {
|
||||
|
||||
private final ListTimeline listTimeline;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller
|
||||
*/
|
||||
public ListViewPane(TimeLineController controller) {
|
||||
super(controller);
|
||||
listTimeline = new ListTimeline(controller);
|
||||
|
||||
//initialize chart;
|
||||
setCenter(listTimeline);
|
||||
setSettingsNodes(new ListViewPane.ListViewSettingsPane().getChildrenUnmodifiable());
|
||||
|
||||
//keep controller's list of selected event IDs in sync with this list's
|
||||
listTimeline.getSelectedEventIDs().addListener((Observable selectedIDs) -> {
|
||||
controller.selectEventIDs(listTimeline.getSelectedEventIDs());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task<Boolean> getNewUpdateTask() {
|
||||
return new ListUpdateTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearData() {
|
||||
listTimeline.clear();
|
||||
}
|
||||
|
||||
private static class ListViewSettingsPane extends Parent {
|
||||
}
|
||||
|
||||
private class ListUpdateTask extends ViewRefreshTask<Interval> {
|
||||
|
||||
ListUpdateTask() {
|
||||
super("List update task", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean call() throws Exception {
|
||||
super.call();
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
FilteredEventsModel eventsModel = getEventsModel();
|
||||
|
||||
//grab the currently selected event
|
||||
HashSet<CombinedEvent> selectedEvents = new HashSet<>(listTimeline.getSelectedEvents());
|
||||
|
||||
//clear the chart and set the time range.
|
||||
resetView(eventsModel.getTimeRange());
|
||||
|
||||
//get the combined events to be displayed
|
||||
updateMessage("Querying DB for events");
|
||||
List<CombinedEvent> combinedEvents = eventsModel.getCombinedEvents();
|
||||
|
||||
updateMessage("Updating UI");
|
||||
Platform.runLater(() -> {
|
||||
//put the combined events into the table.
|
||||
listTimeline.setCombinedEvents(combinedEvents);
|
||||
//restore the selected event
|
||||
listTimeline.selectEvents(selectedEvents);
|
||||
});
|
||||
|
||||
return combinedEvents.isEmpty() == false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
super.cancelled();
|
||||
getController().retreat();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDateValues(Interval timeRange) {
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-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.timeline.ui.listvew;
|
||||
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.MenuElement;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
* Allows creation of JavaFX menus with the same structure as Swing menus and
|
||||
* which invoke the same actions.
|
||||
*/
|
||||
public class SwingFXMenuUtils extends MenuItem {
|
||||
|
||||
/**
|
||||
* Factory method that creates a JavaFX MenuItem backed by a MenuElement
|
||||
*
|
||||
* @param jMenuElement The MenuElement to create a JavaFX menu for.
|
||||
*
|
||||
* @return a MenuItem for the given MenuElement
|
||||
*/
|
||||
public static MenuItem createFXMenu(MenuElement jMenuElement) {
|
||||
if (jMenuElement == null) {
|
||||
//Since null is sometime used to represenet a seperator, follow that convention.
|
||||
return new SeparatorMenuItem();
|
||||
} else if (jMenuElement instanceof JMenu) {
|
||||
return new MenuAdapter((JMenu) jMenuElement);
|
||||
} else if (jMenuElement instanceof JPopupMenu) {
|
||||
return new MenuAdapter((JPopupMenu) jMenuElement);
|
||||
} else {
|
||||
return new MenuItemAdapter((JMenuItem) jMenuElement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A JavaFX MenuItem that invokes the backing JMenuItem when clicked.
|
||||
*/
|
||||
private static class MenuItemAdapter extends MenuItem {
|
||||
|
||||
private MenuItemAdapter(final JMenuItem jMenuItem) {
|
||||
super(jMenuItem.getText());
|
||||
setOnAction(actionEvent -> SwingUtilities.invokeLater(jMenuItem::doClick));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A JavaFX Menu that has the same structure as a given Swing JMenu or
|
||||
* JPopupMenu.
|
||||
*/
|
||||
private static class MenuAdapter extends Menu {
|
||||
|
||||
/**
|
||||
* Constructor for JMenu
|
||||
*
|
||||
* @param jMenu The JMenu to parallel in this Menu.
|
||||
*/
|
||||
MenuAdapter(final JMenu jMenu) {
|
||||
super(jMenu.getText());
|
||||
populateSubMenus(jMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for JPopupMenu
|
||||
*
|
||||
* @param jPopupMenu The JPopupMenu to parallel in this Menu.
|
||||
*/
|
||||
MenuAdapter(JPopupMenu jPopupMenu) {
|
||||
super(jPopupMenu.getLabel());
|
||||
populateSubMenus(jPopupMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the sub menus of this menu.
|
||||
*
|
||||
* @param menu The MenuElement whose sub elements will be used to
|
||||
* populate the sub menus of this menu.
|
||||
*/
|
||||
private void populateSubMenus(MenuElement menu) {
|
||||
for (MenuElement menuElement : menu.getSubElements()) {
|
||||
if (menuElement == null) {
|
||||
//Since null is sometime used to represenet a seperator, follow that convention.
|
||||
getItems().add(new SeparatorMenuItem());
|
||||
|
||||
} else if (menuElement instanceof JMenuItem) {
|
||||
getItems().add(SwingFXMenuUtils.createFXMenu(menuElement));
|
||||
|
||||
} else if (menuElement instanceof JPopupMenu) {
|
||||
populateSubMenus(menuElement);
|
||||
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Unown MenuElement subclass: " + menuElement.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
@ -31,7 +32,7 @@ import javafx.util.StringConverter;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.ViewMode;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
@ -102,15 +103,14 @@ public class ZoomSettingsPane extends TitledPane {
|
||||
Function.identity());
|
||||
descrLODLabel.setText(Bundle.ZoomSettingsPane_descrLODLabel_text());
|
||||
//the description slider is only usefull in the detail view
|
||||
descrLODSlider.disableProperty().bind(controller.visualizationModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(ViewMode.COUNTS));
|
||||
|
||||
/**
|
||||
* In order for the selected value in the time unit slider to correspond
|
||||
* to the amount of time used as units along the x-axis of the
|
||||
* visualization, and since we don't want to show "forever" as a time
|
||||
* unit, the range of the slider is restricted, and there is an offset
|
||||
* of 1 between the "real" value, and what is shown in the slider
|
||||
* labels.
|
||||
* to the amount of time used as units along the x-axis of the view, and
|
||||
* since we don't want to show "forever" as a time unit, the range of
|
||||
* the slider is restricted, and there is an offset of 1 between the
|
||||
* "real" value, and what is shown in the slider labels.
|
||||
*/
|
||||
timeUnitSlider.setMax(TimeUnits.values().length - 2);
|
||||
configureSliderListeners(timeUnitSlider,
|
||||
@ -121,6 +121,12 @@ public class ZoomSettingsPane extends TitledPane {
|
||||
modelTimeRange -> RangeDivisionInfo.getRangeDivisionInfo(modelTimeRange).getPeriodSize().ordinal() - 1,
|
||||
index -> index + 1); //compensate for the -1 above when mapping to the Enum whose displayName will be shown at index
|
||||
timeUnitLabel.setText(Bundle.ZoomSettingsPane_timeUnitLabel_text());
|
||||
|
||||
//hide the whole panel in list mode
|
||||
BooleanBinding notListMode = controller.viewModeProperty().isNotEqualTo(ViewMode.LIST);
|
||||
visibleProperty().bind(notListMode);
|
||||
managedProperty().bind(notListMode);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -176,7 +182,7 @@ public class ZoomSettingsPane extends TitledPane {
|
||||
//set the tick labels to the enum displayNames
|
||||
slider.setLabelFormatter(new EnumSliderLabelFormatter<>(enumClass, labelIndexMapper));
|
||||
|
||||
//make a listener to responds to slider value changes (by updating the visualization)
|
||||
//make a listener to responds to slider value changes (by updating the view)
|
||||
final InvalidationListener sliderListener = observable -> {
|
||||
//only process event if the slider value is not changing (user has released slider thumb)
|
||||
if (slider.isValueChanging() == false) {
|
||||
|
@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javax.swing.JMenu;
|
||||
@ -34,9 +33,7 @@ public class SwingMenuItemAdapter extends MenuItem {
|
||||
SwingMenuItemAdapter(final JMenuItem jMenuItem) {
|
||||
super(jMenuItem.getText());
|
||||
this.jMenuItem = jMenuItem;
|
||||
setOnAction((ActionEvent t) -> {
|
||||
jMenuItem.doClick();
|
||||
});
|
||||
setOnAction(actionEvent -> jMenuItem.doClick());
|
||||
}
|
||||
|
||||
public static MenuItem create(MenuElement jmenuItem) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user