Merge remote-tracking branch 'upstream/TL-list-view' into develop

This commit is contained in:
Richard Cordovano 2016-06-06 17:10:48 -04:00
commit c398dc4536
42 changed files with 2320 additions and 1033 deletions

View File

@ -1,15 +1,15 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-2015 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.actions; package org.sleuthkit.autopsy.actions;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
@ -26,8 +27,8 @@ import org.openide.util.NbBundle;
import org.openide.util.Utilities; import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException; 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 // 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 // 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). // node in the array returns a reference to the same action object from Node.getActions(boolean).
private static AddBlackboardArtifactTagAction instance; private static AddBlackboardArtifactTagAction instance;
public static synchronized AddBlackboardArtifactTagAction getInstance() { public static synchronized AddBlackboardArtifactTagAction getInstance() {
@ -63,7 +63,14 @@ public class AddBlackboardArtifactTagAction extends AddTagAction {
@Override @Override
protected void addTag(TagName tagName, String comment) { 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(() -> { new Thread(() -> {
for (BlackboardArtifact artifact : selectedArtifacts) { for (BlackboardArtifact artifact : selectedArtifacts) {

View File

@ -1,15 +1,15 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-2015 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -19,13 +19,13 @@
package org.sleuthkit.autopsy.actions; package org.sleuthkit.autopsy.actions;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.Utilities; import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content; 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 // 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 // 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). // node in the array returns a reference to the same action object from Node.getActions(boolean).
private static AddContentTagAction instance; private static AddContentTagAction instance;
public static synchronized AddContentTagAction getInstance() { public static synchronized AddContentTagAction getInstance() {
@ -63,7 +62,13 @@ public class AddContentTagAction extends AddTagAction {
@Override @Override
protected void addTag(TagName tagName, String comment) { 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(() -> { new Thread(() -> {
for (AbstractFile file : selectedFiles) { for (AbstractFile file : selectedFiles) {

View File

@ -9,7 +9,7 @@ Timeline.zoomOutButton.text=Zoom Out
Timeline.goToButton.text=Go To\: Timeline.goToButton.text=Go To\:
Timeline.yearBarChart.x.years=Years Timeline.yearBarChart.x.years=Years
Timeline.resultPanel.loading=Loading... Timeline.resultPanel.loading=Loading...
Timeline.node.root=Root
TimelineFrame.title=Timeline TimelineFrame.title=Timeline
TimelinePanel.jButton1.text=6m TimelinePanel.jButton1.text=6m
TimelinePanel.jButton13.text=all TimelinePanel.jButton13.text=all
@ -24,4 +24,4 @@ TimelinePanel.jButton7.text=3d
TimelinePanel.jButton2.text=1m TimelinePanel.jButton2.text=1m
TimelinePanel.jButton3.text=3m TimelinePanel.jButton3.text=3m
TimelinePanel.jButton4.text=2w TimelinePanel.jButton4.text=2w
ProgressWindow.progressHeader.text=\ ProgressWindow.progressHeader.text=\

View File

@ -1,48 +1,48 @@
CTL_MakeTimeline=\u300C\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u300D CTL_MakeTimeline=\u300c\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u300d
CTL_TimeLineTopComponent=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A6\u30A3\u30F3\u30C9\u30A6 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 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 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 OpenTimelineAction.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3
Timeline.frameName.text={0} - Autopsy\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.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.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.resultPanel.loading=\u30ED\u30FC\u30C9\u4E2D\u30FB\u30FB\u30FB Timeline.resultsPanel.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u7d50\u679c
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.runJavaFxThread.progress.creating=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u4F5C\u6210\u4E2D\u30FB\u30FB\u30FB Timeline.zoomOutButton.text=\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8
Timeline.zoomOutButton.text=\u30BA\u30FC\u30E0\u30A2\u30A6\u30C8 TimelineFrame.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3
TimelineFrame.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3 TimeLineTopComponent.eventsTab.name=\u30a4\u30d9\u30f3\u30c8
TimeLineTopComponent.eventsTab.name=\u30A4\u30D9\u30F3\u30C8 TimeLineTopComponent.filterTab.name=\u30d5\u30a3\u30eb\u30bf\u30fc
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
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.continueNoUpdate=\u66F4\u65B0\u305B\u305A\u6B21\u3078 PrompDialogManager.buttonType.showTimeline=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u8868\u793a
PrompDialogManager.buttonType.showTimeline=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u8868\u793A PrompDialogManager.buttonType.update=\u66f4\u65b0
PrompDialogManager.buttonType.update=\u66F4\u65B0 PromptDialogManager.confirmDuringIngest.contentText=\u6b21\u3078\u9032\u307f\u307e\u3059\u304b\uff1f
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.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.progressDialog.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u3092\u5165\u529B\u4E2D PromptDialogManager.rebuildPrompt.details=\u8a73\u7d30\uff1a
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
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.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
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.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.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.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.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.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.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
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 TimelinePanel.jButton13.text=\u5168\u3066
Timeline.yearBarChart.x.years=\u5E74 Timeline.yearBarChart.x.years=\u5e74
TimelinePanel.jButton1.text=6\u30F6\u6708 TimelinePanel.jButton1.text=6\u30f6\u6708
TimelinePanel.jButton10.text=1\u6642\u9593 TimelinePanel.jButton10.text=1\u6642\u9593
TimelinePanel.jButton9.text=12\u6642\u9593 TimelinePanel.jButton9.text=12\u6642\u9593
TimelinePanel.jButton11.text=5\u5E74 TimelinePanel.jButton11.text=5\u5e74
TimelinePanel.jButton12.text=10\u5E74 TimelinePanel.jButton12.text=10\u5e74
TimelinePanel.jButton6.text=1\u9031\u9593 TimelinePanel.jButton6.text=1\u9031\u9593
TimelinePanel.jButton5.text=1\u5E74 TimelinePanel.jButton5.text=1\u5e74
TimelinePanel.jButton8.text=1\u65E5 TimelinePanel.jButton8.text=1\u65e5
TimelinePanel.jButton7.text=3\u65E5 TimelinePanel.jButton7.text=3\u65e5
TimelinePanel.jButton2.text=1\u30F6\u6708 TimelinePanel.jButton2.text=1\u30f6\u6708
TimelinePanel.jButton3.text=3\u30F6\u6708 TimelinePanel.jButton3.text=3\u30f6\u6708
TimelinePanel.jButton4.text=2\u9031\u9593 TimelinePanel.jButton4.text=2\u9031\u9593
TimeLineResultView.startDateToEndDate.text={0}\u304b\u3089{1}

View File

@ -24,6 +24,7 @@ import java.io.IOException;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -188,7 +189,7 @@ public class TimeLineController {
} }
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private TimeLineTopComponent mainFrame; private TimeLineTopComponent topComponent;
//are the listeners currently attached //are the listeners currently attached
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@ -199,11 +200,7 @@ public class TimeLineController {
private final PropertyChangeListener ingestModuleListener = new AutopsyIngestModuleListener(); private final PropertyChangeListener ingestModuleListener = new AutopsyIngestModuleListener();
@GuardedBy("this") @GuardedBy("this")
private final ReadOnlyObjectWrapper<VisualizationMode> visualizationMode = new ReadOnlyObjectWrapper<>(VisualizationMode.COUNTS); private final ReadOnlyObjectWrapper<ViewMode> viewMode = new ReadOnlyObjectWrapper<>(ViewMode.COUNTS);
synchronized public ReadOnlyObjectProperty<VisualizationMode> visualizationModeProperty() {
return visualizationMode.getReadOnlyProperty();
}
@GuardedBy("filteredEvents") @GuardedBy("filteredEvents")
private final FilteredEventsModel filteredEvents; private final FilteredEventsModel filteredEvents;
@ -223,21 +220,38 @@ public class TimeLineController {
@GuardedBy("this") @GuardedBy("this")
private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>synchronizedObservableList(FXCollections.<Long>observableArrayList()); 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() { synchronized public ObservableList<Long> getSelectedEventIDs() {
return selectedEventIDs; 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() { synchronized public Interval getSelectedTimeRange() {
return selectedTimeRange.getReadOnlyProperty(); return selectedTimeRange.get();
} }
public ReadOnlyBooleanProperty eventsDBStaleProperty() { public ReadOnlyBooleanProperty eventsDBStaleProperty() {
@ -282,9 +296,30 @@ public class TimeLineController {
synchronized public ReadOnlyBooleanProperty canRetreatProperty() { synchronized public ReadOnlyBooleanProperty canRetreatProperty() {
return historyManager.getCanRetreat(); 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 { public TimeLineController(Case autoCase) throws IOException {
this.autoCase = autoCase; this.autoCase = autoCase;
@ -310,6 +345,9 @@ public class TimeLineController {
filteredEvents.filterProperty().get(), filteredEvents.filterProperty().get(),
DescriptionLoD.SHORT); DescriptionLoD.SHORT);
historyManager.advance(InitialZoomState); 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().removeIngestModuleEventListener(ingestModuleListener);
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener); IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
Case.removePropertyChangeListener(caseListener); Case.removePropertyChangeListener(caseListener);
if (mainFrame != null) { if (topComponent != null) {
mainFrame.close(); topComponent.close();
mainFrame = null; topComponent = null;
} }
OpenTimelineAction.invalidateController(); OpenTimelineAction.invalidateController();
} }
@ -582,17 +620,6 @@ public class TimeLineController {
pushTimeRange(new Interval(start, end)); 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) { public void selectEventIDs(Collection<Long> events) {
final LoggedTask<Interval> selectEventIDsTask = new LoggedTask<Interval>("Select Event IDs", true) { //NON-NLS final LoggedTask<Interval> selectEventIDsTask = new LoggedTask<Interval>("Select Event IDs", true) { //NON-NLS
@Override @Override
@ -624,16 +651,16 @@ public class TimeLineController {
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
synchronized private void showWindow() { synchronized private void showWindow() {
if (mainFrame == null) { if (topComponent == null) {
mainFrame = new TimeLineTopComponent(this); topComponent = new TimeLineTopComponent(this);
} }
mainFrame.open(); topComponent.open();
mainFrame.toFront(); topComponent.toFront();
/* /*
* Make this top component active so its ExplorerManager's lookup gets * Make this top component active so its ExplorerManager's lookup gets
* proxied in Utilities.actionsGlobalContext() * proxied in Utilities.actionsGlobalContext()
*/ */
mainFrame.requestActive(); topComponent.requestActive();
} }
synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) { synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) {

View File

@ -54,7 +54,7 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents> <SubComponents>
<Container class="javafx.embed.swing.JFXPanel" name="jFXVizPanel"> <Container class="javafx.embed.swing.JFXPanel" name="jFXViewPanel">
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/> <JSplitPaneConstraints position="left"/>
@ -65,7 +65,7 @@
<Property name="useNullLayout" type="boolean" value="true"/> <Property name="useNullLayout" type="boolean" value="true"/>
</Layout> </Layout>
</Container> </Container>
<Container class="javax.swing.JSplitPane" name="lowerSplitXPane"> <Container class="javax.swing.JSplitPane" name="horizontalSplitPane">
<Properties> <Properties>
<Property name="dividerLocation" type="int" value="600"/> <Property name="dividerLocation" type="int" value="600"/>
<Property name="resizeWeight" type="double" value="0.5"/> <Property name="resizeWeight" type="double" value="0.5"/>
@ -82,33 +82,45 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents> <SubComponents>
<Container class="javax.swing.JPanel" name="resultContainerPanel"> <Container class="javax.swing.JPanel" name="leftFillerPanel">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 300]"/>
</Property>
</Properties>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/> <JSplitPaneConstraints position="left"/>
</Constraint> </Constraint>
</Constraints> </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>
<Container class="javax.swing.JPanel" name="contentViewerContainerPanel"> <Container class="javax.swing.JPanel" name="rightfillerPanel">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[500, 300]"/>
</Property>
</Properties>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/> <JSplitPaneConstraints position="right"/>
</Constraint> </Constraint>
</Constraints> </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> </Container>
</SubComponents> </SubComponents>
</Container> </Container>

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-15 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,12 +18,14 @@
*/ */
package org.sleuthkit.autopsy.timeline; package org.sleuthkit.autopsy.timeline;
import java.awt.BorderLayout; import java.beans.PropertyVetoException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.embed.swing.JFXPanel; import javafx.collections.ObservableList;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.SplitPane; import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
@ -34,8 +36,15 @@ import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox; 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.ExplorerManager;
import org.openide.explorer.ExplorerUtils; 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.util.NbBundle;
import org.openide.windows.Mode; import org.openide.windows.Mode;
import org.openide.windows.TopComponent; 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.DataContentPanel;
import org.sleuthkit.autopsy.corecomponents.DataResultPanel; import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
import org.sleuthkit.autopsy.coreutils.Logger; 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.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward; 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.HistoryToolBar;
import org.sleuthkit.autopsy.timeline.ui.StatusBar; 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.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.detailview.tree.EventsTree;
import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel; import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel;
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane; import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
import org.sleuthkit.datamodel.TskCoreException;
/** /**
* TopComponent for the timeline feature. * TopComponent for the Timeline feature.
*/ */
@TopComponent.Description( @TopComponent.Description(
preferredID = "TimeLineTopComponent", 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 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 ExplorerManager em = new ExplorerManager();
private final TimeLineController controller; 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) { public TimeLineTopComponent(TimeLineController controller) {
initComponents(); initComponents();
this.controller = controller;
associateLookup(ExplorerUtils.createLookup(em, getActionMap())); associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent")); setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent")); setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
dataContentPanel = DataContentPanel.createInstance(); this.controller = controller;
this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER);
tlrv = new TimeLineResultView(controller, dataContentPanel);
DataResultPanel dataResultPanel = tlrv.getDataResultPanel();
this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
dataResultPanel.open();
customizeFXComponents();
}
@NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events", //create linked result and content views
"TimeLineTopComponent.filterTab.name=Filters"}) contentViewerPanel = DataContentPanel.createInstance();
void customizeFXComponents() { dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, contentViewerPanel);
Platform.runLater(() -> {
//create and wire up jfx componenets that make up the interface //add them to bottom splitpane
final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller)); horizontalSplitPane.setLeftComponent(dataResultPanel);
filterTab.setClosable(false); horizontalSplitPane.setRightComponent(contentViewerPanel);
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
final EventsTree eventsTree = new EventsTree(controller); dataResultPanel.open(); //get the explorermanager
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));
final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab); Platform.runLater(this::initFXComponents);
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);
}
});
HistoryToolBar historyToolBar = new HistoryToolBar(controller); //set up listeners
final TimeZonePanel timeZonePanel = new TimeZonePanel(); TimeLineController.getTimeZone().addListener(timeZone -> dataResultPanel.setPath(getResultViewerSummaryString()));
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES); controller.getSelectedEventIDs().addListener(selectedEventsListener);
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller); //Listen to ViewMode and adjust GUI componenets as needed.
controller.viewModeProperty().addListener(viewMode -> {
final VBox leftVBox = new VBox(5, timeZonePanel,historyToolBar, zoomSettingsPane, leftTabPane); switch (controller.getViewMode()) {
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE); case COUNTS:
case DETAIL:
final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel); /*
mainSplitPane.setDividerPositions(0); * For counts and details mode, restore the result table at
* the bottom left.
final Scene scene = new Scene(mainSplitPane); */
scene.addEventFilter(KeyEvent.KEY_PRESSED, SwingUtilities.invokeLater(() -> {
(KeyEvent event) -> { splitYPane.remove(contentViewerPanel);
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) { if ((horizontalSplitPane.getParent() == splitYPane) == false) {
new Back(controller).handle(null); splitYPane.setBottomComponent(horizontalSplitPane);
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) { horizontalSplitPane.setRightComponent(contentViewerPanel);
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);
} }
}); });
break;
//add ui componenets to JFXPanels case LIST:
jFXVizPanel.setScene(scene); /*
jFXstatusPanel.setScene(new Scene(new StatusBar(controller))); * 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 @Override
public List<Mode> availableModes(List<Mode> modes) { public List<Mode> availableModes(List<Mode> modes) {
return Collections.emptyList(); 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 // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
jFXstatusPanel = new JFXPanel(); jFXstatusPanel = new javafx.embed.swing.JFXPanel();
splitYPane = new javax.swing.JSplitPane(); splitYPane = new javax.swing.JSplitPane();
jFXVizPanel = new JFXPanel(); jFXViewPanel = new javafx.embed.swing.JFXPanel();
lowerSplitXPane = new javax.swing.JSplitPane(); horizontalSplitPane = new javax.swing.JSplitPane();
resultContainerPanel = new javax.swing.JPanel(); leftFillerPanel = new javax.swing.JPanel();
contentViewerContainerPanel = new javax.swing.JPanel(); rightfillerPanel = new javax.swing.JPanel();
jFXstatusPanel.setPreferredSize(new java.awt.Dimension(100, 16)); 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.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
splitYPane.setResizeWeight(0.9); splitYPane.setResizeWeight(0.9);
splitYPane.setPreferredSize(new java.awt.Dimension(1024, 400)); splitYPane.setPreferredSize(new java.awt.Dimension(1024, 400));
splitYPane.setLeftComponent(jFXVizPanel); splitYPane.setLeftComponent(jFXViewPanel);
lowerSplitXPane.setDividerLocation(600); horizontalSplitPane.setDividerLocation(600);
lowerSplitXPane.setResizeWeight(0.5); horizontalSplitPane.setResizeWeight(0.5);
lowerSplitXPane.setPreferredSize(new java.awt.Dimension(1200, 300)); horizontalSplitPane.setPreferredSize(new java.awt.Dimension(1200, 300));
lowerSplitXPane.setRequestFocusEnabled(false); horizontalSplitPane.setRequestFocusEnabled(false);
resultContainerPanel.setPreferredSize(new java.awt.Dimension(700, 300)); javax.swing.GroupLayout leftFillerPanelLayout = new javax.swing.GroupLayout(leftFillerPanel);
resultContainerPanel.setLayout(new java.awt.BorderLayout()); leftFillerPanel.setLayout(leftFillerPanelLayout);
lowerSplitXPane.setLeftComponent(resultContainerPanel); 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)); horizontalSplitPane.setLeftComponent(leftFillerPanel);
contentViewerContainerPanel.setLayout(new java.awt.BorderLayout());
lowerSplitXPane.setRightComponent(contentViewerContainerPanel);
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); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout); this.setLayout(layout);
layout.setHorizontalGroup( layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(splitYPane, javax.swing.GroupLayout.DEFAULT_SIZE, 972, Short.MAX_VALUE) .addComponent(splitYPane, javax.swing.GroupLayout.DEFAULT_SIZE, 972, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup() .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)
.addGap(0, 0, 0))
); );
layout.setVerticalGroup( layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@ -215,11 +360,11 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel contentViewerContainerPanel; private javax.swing.JSplitPane horizontalSplitPane;
private javafx.embed.swing.JFXPanel jFXVizPanel; private javafx.embed.swing.JFXPanel jFXViewPanel;
private javafx.embed.swing.JFXPanel jFXstatusPanel; private javafx.embed.swing.JFXPanel jFXstatusPanel;
private javax.swing.JSplitPane lowerSplitXPane; private javax.swing.JPanel leftFillerPanel;
private javax.swing.JPanel resultContainerPanel; private javax.swing.JPanel rightfillerPanel;
private javax.swing.JSplitPane splitYPane; private javax.swing.JSplitPane splitYPane;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
@ -229,25 +374,34 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
putClientProperty(PROP_UNDOCKING_DISABLED, true); 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 @Override
public ExplorerManager getExplorerManager() { public ExplorerManager getExplorerManager() {
return em; 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);
}
}
} }

View File

@ -21,7 +21,9 @@ package org.sleuthkit.autopsy.timeline;
/** /**
* *
*/ */
public enum VisualizationMode { public enum ViewMode {
COUNTS, DETAIL; COUNTS,
DETAIL,
LIST;
} }

View File

@ -71,7 +71,7 @@ public class SaveSnapshotAsReport extends Action {
"Timeline.ModuleName=Timeline", "Timeline.ModuleName=Timeline",
"SaveSnapShotAsReport.action.dialogs.title=Timeline", "SaveSnapShotAsReport.action.dialogs.title=Timeline",
"SaveSnapShotAsReport.action.name.text=Snapshot Report", "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", "# {0} - report file path",
"SaveSnapShotAsReport.ReportSavedAt=Report saved at [{0}]", "SaveSnapShotAsReport.ReportSavedAt=Report saved at [{0}]",
"SaveSnapShotAsReport.Success=Success", "SaveSnapShotAsReport.Success=Success",

View File

@ -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;
}
}

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-16 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,10 +21,10 @@ package org.sleuthkit.autopsy.timeline.datamodel;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import org.joda.time.Interval; import org.joda.time.Interval;
@ -108,8 +108,8 @@ public class EventCluster implements MultiEvent<EventStripe> {
*/ */
private final ImmutableSet<Long> hashHits; private final ImmutableSet<Long> hashHits;
private EventCluster(Interval spanningInterval, EventType type, Set<Long> eventIDs, private EventCluster(Interval spanningInterval, EventType type, Collection<Long> eventIDs,
Set<Long> hashHits, Set<Long> tagged, String description, DescriptionLoD lod, Collection<Long> hashHits, Collection<Long> tagged, String description, DescriptionLoD lod,
EventStripe parent) { EventStripe parent) {
this.span = spanningInterval; this.span = spanningInterval;
@ -122,8 +122,8 @@ public class EventCluster implements MultiEvent<EventStripe> {
this.parent = parent; this.parent = parent;
} }
public EventCluster(Interval spanningInterval, EventType type, Set<Long> eventIDs, public EventCluster(Interval spanningInterval, EventType type, Collection<Long> eventIDs,
Set<Long> hashHits, Set<Long> tagged, String description, DescriptionLoD lod) { Collection<Long> hashHits, Collection<Long> tagged, String description, DescriptionLoD lod) {
this(spanningInterval, type, eventIDs, hashHits, tagged, description, lod, null); this(spanningInterval, type, eventIDs, hashHits, tagged, description, lod, null);
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2014-15 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -70,15 +70,15 @@ import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException; 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. * Views can register listeners on properties returned by methods.
* *
* This class is implemented as a filtered view into an underlying * 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 * 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 * Concurrency Policy: repo is internally synchronized, so methods that only
* access the repo atomically do not need further synchronization * access the repo atomically do not need further synchronization
@ -279,7 +279,7 @@ public final class FilteredEventsModel {
return repo.getTagCountsByTagName(eventIDsWithTags); return repo.getTagCountsByTagName(eventIDsWithTags);
} }
public Set<Long> getEventIDs(Interval timeRange, Filter filter) { public List<Long> getEventIDs(Interval timeRange, Filter filter) {
final Interval overlap; final Interval overlap;
final RootFilter intersect; final RootFilter intersect;
synchronized (this) { synchronized (this) {
@ -290,6 +290,21 @@ public final class FilteredEventsModel {
return repo.getEventIDs(overlap, intersect); 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 * return the number of events that pass the requested filter and are within
* the given time range. * the given time range.

View File

@ -56,6 +56,7 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.coreutils.Version;
import org.sleuthkit.autopsy.timeline.TimeLineController; 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.EventCluster;
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
@ -343,18 +344,29 @@ public class EventDB {
return result; 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)) { 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(); 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(); try (Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query)) { ResultSet rs = stmt.executeQuery(query)) {
while (rs.next()) { while (rs.next()) {
@ -370,6 +382,55 @@ public class EventDB {
return resultIDs; 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 * 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. * 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 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 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 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 dropEventsTableStmt = prepareStatement("DROP TABLE IF EXISTS events"); //NON-NLS
dropHashSetHitsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_set_hits"); //NON-NLS dropHashSetHitsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_set_hits"); //NON-NLS
dropHashSetsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_sets"); //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 { 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 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 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)); 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 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 List<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> tagged = SQLHelper.unGroupConcat(rs.getString("taggeds"), Long::valueOf); //NON-NLS
return new EventCluster(interval, type, eventIDs, hashHits, tagged, description, descriptionLOD); 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 return useSubTypes ? "sub_type" : "base_type"; //NON-NLS
} }
private PreparedStatement prepareStatement(String queryString) throws SQLException { private PreparedStatement prepareStatement(String queryString) throws SQLException {
PreparedStatement prepareStatement = con.prepareStatement(queryString); PreparedStatement prepareStatement = con.prepareStatement(queryString);
preparedStatements.add(prepareStatement); preparedStatements.add(prepareStatement);

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-15 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -56,6 +56,7 @@ import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.CancellationProgressTask; 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.EventStripe;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
@ -214,10 +215,25 @@ public class EventsRepository {
idToEventCache.invalidateAll(); idToEventCache.invalidateAll();
} }
public Set<Long> getEventIDs(Interval timeRange, RootFilter filter) { public List<Long> getEventIDs(Interval timeRange, RootFilter filter) {
return eventDB.getEventIDs(timeRange, 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) { public Interval getSpanningInterval(Collection<Long> eventIDs) {
return eventDB.getSpanningInterval(eventIDs); return eventDB.getSpanningInterval(eventIDs);
} }

View File

@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.timeline.db;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; 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 * @return a Set of X, each element mapped from one element of the original
* comma delimited string * comma delimited string
*/ */
static <X> Set<X> unGroupConcat(String groupConcat, Function<String, X> mapper) { static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
return StringUtils.isBlank(groupConcat) ? Collections.emptySet() return StringUtils.isBlank(groupConcat) ? Collections.emptyList()
: Stream.of(groupConcat.split(",")) : Stream.of(groupConcat.split(","))
.map(mapper::apply) .map(mapper::apply)
.collect(Collectors.toSet()); .collect(Collectors.toList());
} }
/** /**

View File

@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.timeline.events;
/** /**
* A "local" event published by filteredEventsModel to indicate that the user * 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.) * 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. * This event is not intended for use out side of the Timeline module.

View File

@ -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

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2014 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -23,47 +23,60 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.beans.Observable;
import javax.swing.Action; import javax.swing.Action;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import org.openide.nodes.Children; import org.openide.nodes.Children;
import org.openide.nodes.PropertySupport; import org.openide.nodes.PropertySupport;
import org.openide.nodes.Sheet; import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups; import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content; 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 static final Logger LOGGER = Logger.getLogger(EventNode.class.getName());
private final SingleEvent e; private final SingleEvent event;
EventNode(SingleEvent eventById, AbstractFile file, BlackboardArtifact artifact) { EventNode(SingleEvent event, AbstractFile file, BlackboardArtifact artifact) {
super(Children.LEAF, Lookups.fixed(eventById, file, artifact)); super(Children.LEAF, Lookups.fixed(event, file, artifact));
this.e = eventById; this.event = event;
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getEventType().getIconBase()); // NON-NLS this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + event.getEventType().getIconBase()); // NON-NLS
} }
EventNode(SingleEvent eventById, AbstractFile file) { EventNode(SingleEvent event, AbstractFile file) {
super(Children.LEAF, Lookups.fixed(eventById, file)); super(Children.LEAF, Lookups.fixed(event, file));
this.e = eventById; this.event = event;
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getEventType().getIconBase()); // NON-NLS this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + event.getEventType().getIconBase()); // NON-NLS
} }
@Override @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() { protected Sheet createSheet() {
Sheet s = super.createSheet(); Sheet s = super.createSheet();
Sheet.Set properties = s.get(Sheet.PROPERTIES); Sheet.Set properties = s.get(Sheet.PROPERTIES);
@ -72,28 +85,25 @@ class EventNode extends DisplayableItemNode {
s.put(properties); s.put(properties);
} }
final TimeProperty timePropery = new TimeProperty("time", "Date/Time", "time ", getDateTimeString()); // 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
TimeLineController.getTimeZone().addListener((Observable observable) -> { properties.put(new NodeProperty<>("description", Bundle.NodeProperty_displayName_description(), "description", event.getFullDescription())); // NON-NLS
try { properties.put(new NodeProperty<>("eventBaseType", Bundle.NodeProperty_displayName_baseType(), "base type", event.getEventType().getSuperType().getDisplayName())); // NON-NLS
timePropery.setValue(getDateTimeString()); properties.put(new NodeProperty<>("eventSubType", Bundle.NodeProperty_displayName_subType(), "sub type", event.getEventType().getDisplayName())); // NON-NLS
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { properties.put(new NodeProperty<>("Known", Bundle.NodeProperty_displayName_known(), "known", event.getKnown().toString())); // NON-NLS
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
return s; 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() { private String getDateTimeString() {
return new DateTime(e.getStartMillis(), DateTimeZone.UTC).toString(TimeLineController.getZonedFormatter()); return new DateTime(event.getStartMillis(), DateTimeZone.UTC).toString(TimeLineController.getZonedFormatter());
} }
@Override @Override
@ -118,7 +128,7 @@ class EventNode extends DisplayableItemNode {
@Override @Override
public <T> T accept(DisplayableItemNodeVisitor<T> dinv) { 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 * We use TimeProperty instead of a normal NodeProperty to correctly display
* the date/time when the user changes the timezone setting. * 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; private String value;
@ -147,6 +157,14 @@ class EventNode extends DisplayableItemNode {
super(name, String.class, displayName, shortDescription); super(name, String.class, displayName, shortDescription);
setValue("suppressCustomEditor", Boolean.TRUE); // remove the "..." (editing) button NON-NLS setValue("suppressCustomEditor", Boolean.TRUE); // remove the "..." (editing) button NON-NLS
this.value = value; 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 @Override
@ -161,4 +179,32 @@ class EventNode extends DisplayableItemNode {
firePropertyChange("time", oldValue, t); // NON-NLS 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);
}
}
} }

View File

@ -27,22 +27,19 @@ import org.openide.nodes.Children;
import org.openide.nodes.Node; import org.openide.nodes.Node;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups; import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; 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; import org.sleuthkit.datamodel.TskCoreException;
/** /**
* Root Explorer node to represent events. * Root Explorer Node to represent events.
*/ */
public class EventRootNode extends DisplayableItemNode { 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 * 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 * 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; public static final int MAX_EVENTS_TO_DISPLAY = 5000;
/** public EventRootNode(Collection<Long> fileIds, FilteredEventsModel filteredEvents) {
* the number of child events
*/
private final int childCount;
public EventRootNode(String NAME, Collection<Long> fileIds, FilteredEventsModel filteredEvents) {
super(Children.create(new EventNodeChildFactory(fileIds, filteredEvents), true), Lookups.singleton(fileIds)); super(Children.create(new EventNodeChildFactory(fileIds, filteredEvents), true), Lookups.singleton(fileIds));
super.setName(NAME);
super.setDisplayName(NAME);
childCount = fileIds.size();
} }
@Override @Override
@ -74,9 +61,6 @@ public class EventRootNode extends DisplayableItemNode {
return null; return null;
} }
public int getChildCount() {
return childCount;
}
/* /*
* TODO (AUT-1849): Correct or remove peristent column reordering code * 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()); 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; 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; private final FilteredEventsModel filteredEvents;
@ -112,8 +96,8 @@ public class EventRootNode extends DisplayableItemNode {
@Override @Override
protected boolean createKeys(List<Long> toPopulate) { protected boolean createKeys(List<Long> toPopulate) {
/** /**
* if there are too many events, just add one id (-1) to indicate * If there are too many events, just add one dummy ID (-1) to
* this. * indicate this.
*/ */
if (eventIDs.size() < MAX_EVENTS_TO_DISPLAY) { if (eventIDs.size() < MAX_EVENTS_TO_DISPLAY) {
toPopulate.addAll(eventIDs); toPopulate.addAll(eventIDs);
@ -127,34 +111,24 @@ public class EventRootNode extends DisplayableItemNode {
protected Node createNodeForKey(Long eventID) { protected Node createNodeForKey(Long eventID) {
if (eventID < 0) { if (eventID < 0) {
/* /*
* if the eventId is a the special value, return a node with a * If the eventId is a the special value ( -1 ), return a node
* warning that their are too many evens * with a warning that their are too many evens
*/ */
return new TooManyNode(eventIDs.size()); return new TooManyNode(eventIDs.size());
} else { } 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 { try {
SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); return EventNode.createEventNode(eventID, filteredEvents);
AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID()); } catch (IllegalStateException ex) {
if (file != null) { //Since the case is closed, the user probably doesn't care about this, just log it as a precaution.
if (eventById.getArtifactID().isPresent()) { LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get()); return null;
return new EventNode(eventById, file, blackboardArtifact); } catch (TskCoreException ex) {
} else { /*
return new EventNode(eventById, file); * Just log it: There might be lots of these errors, and we
} * don't want to flood the user with notifications. It will
} else { * be obvious the UI is broken anyways
//This should never happen in normal operations */
LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent."); // NON-NLS LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // 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 null; 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 * A Node with a warning message that their are too many events to show.
* to show
*/ */
private static class TooManyNode extends AbstractNode { private static class TooManyNode extends AbstractNode {

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

View File

@ -70,7 +70,7 @@ public class SnapShotReportWriter {
* @param zoomParams The ZoomParams in effect when the snapshot was * @param zoomParams The ZoomParams in effect when the snapshot was
* taken. * taken.
* @param generationDate The generation Date of the report. * @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. * report.
*/ */
public SnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, ZoomParams zoomParams, Date generationDate, BufferedImage snapshot) { public SnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, ZoomParams zoomParams, Date generationDate, BufferedImage snapshot) {

View File

@ -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);
});
}
}
}

View File

@ -18,28 +18,16 @@
*/ */
package org.sleuthkit.autopsy.timeline.ui; 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.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.DoubleBinding; import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList; import javafx.collections.transformation.SortedList;
import javafx.concurrent.Task;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.chart.Axis; import javafx.scene.chart.Axis;
import javafx.scene.chart.XYChart; import javafx.scene.chart.XYChart;
@ -47,7 +35,6 @@ import javafx.scene.control.Label;
import javafx.scene.control.OverrunStyle; import javafx.scene.control.OverrunStyle;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
import javafx.scene.layout.Border; import javafx.scene.layout.Border;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.BorderStroke; import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle; import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths; import javafx.scene.layout.BorderWidths;
@ -55,7 +42,6 @@ import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.text.Font; import javafx.scene.text.Font;
@ -64,18 +50,14 @@ import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment; import javafx.scene.text.TextAlignment;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.controlsfx.control.MaskerPane;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.TimeLineController; 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.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 <X> The type of data plotted along the x axis
* @param <Y> The type of data plotted along the y 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 * 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.") @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.AbstractVisualization_Default_Tooltip_text()); 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))); 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 * Get the tool tip to use for this view when no more specific Tooltip is
* Tooltip is needed. * needed.
* *
* @return The default Tooltip. * @return The default Tooltip.
*/ */
public static Tooltip getDefaultTooltip() { static public Tooltip getDefaultTooltip() {
return DEFAULT_TOOLTIP; return DEFAULT_TOOLTIP;
} }
/** /**
* Boolean property that holds true if the visualization may not represent * The nodes that are selected.
* the current state of the DB, because, for example, tags have been updated *
* but the vis. was not refreshed. * @return An ObservableList<NodeType> of the nodes that are selected in
* this view.
*/ */
private final ReadOnlyBooleanWrapper outOfDate = new ReadOnlyBooleanWrapper(false); protected ObservableList<NodeType> getSelectedNodes() {
return selectedNodes;
/** }
* 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);
/** /**
* Access to chart data via series * Access to chart data via series
@ -130,123 +109,36 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
//// replacement axis label componenets //// replacement axis label componenets
private final Pane specificLabelPane = new Pane(); // container for the specfic labels in the decluttered axis 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 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(); 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(); final private ObservableList<NodeType> selectedNodes = FXCollections.observableArrayList();
/** public Pane getSpecificLabelPane() {
* Listener that is attached to various properties that should trigger a vis return specificLabelPane;
* update when they change. }
*/
private InvalidationListener updateListener = any -> refresh();
/** public Pane getContextLabelPane() {
* Does the visualization represent an out-of-date state of the DB. It might return contextLabelPane;
* 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 public Region getSpacer() {
* the DB. return spacer;
*/
public boolean isOutOfDate() {
return outOfDate.get();
} }
/** /**
* Set this visualization out of date because, for example, tags have been * Get the CharType that implements this view.
* 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>
* *
* @return A ReadOnlyBooleanProperty that holds the out-of-date state for * @return The CharType that implements this view.
* 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.
*/ */
protected ChartType getChart() { protected ChartType getChart() {
return chart; return chart;
} }
/** /**
* Get the FilteredEventsModel for this visualization. * Set the ChartType that implements this view.
* *
* @return The FilteredEventsModel for this visualization. * @param chart The ChartType that implements this view.
*/
protected FilteredEventsModel getEventsModel() {
return filteredEvents;
}
/**
* Set the ChartType that implements this visualization.
*
* @param chart The ChartType that implements this visualization.
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
protected void setChart(ChartType chart) { 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 * Apply this view's 'selection effect' to the given node.
* 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.
* *
* @param node The node to apply the 'effect' to. * @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. * @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 * Should the tick mark at the given value be bold, because it has
* interesting data associated with it? * 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 * @return True if the tick label for the given value should be bold ( has
* relevant data), false otherwise * relevant data), false otherwise
@ -306,8 +176,8 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
abstract protected Boolean isTickBold(X value); abstract protected Boolean isTickBold(X value);
/** /**
* Apply this visualization's 'selection effect' to the given node, if * Apply this view's 'selection effect' to the given node, if applied is
* applied is true. If applied is false, remove the affect * true. If applied is false, remove the affect
* *
* @param node The node to apply the 'effect' to * @param node The node to apply the 'effect' to
* @param applied True if the effect should be applied, false if the effect * @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); 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. * 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(); 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(); 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(); abstract protected Axis<Y> getYAxis();
@ -364,74 +225,6 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
*/ */
abstract protected double getAxisMargin(); 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. * 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 * Constructor
* *
* @param controller The TimelineController for this visualization. * @param controller The TimelineController for this view.
*/ */
protected AbstractVisualizationPane(TimeLineController controller) { protected AbstractTimelineChart(TimeLineController controller) {
this.controller = controller; super(controller);
this.filteredEvents = controller.getEventsModel();
this.filteredEvents.registerForEvents(this);
this.filteredEvents.zoomParametersProperty().addListener(updateListener);
Platform.runLater(() -> { Platform.runLater(() -> {
VBox vBox = new VBox(specificLabelPane, contextLabelPane); VBox vBox = new VBox(getSpecificLabelPane(), getContextLabelPane());
vBox.setFillWidth(false); vBox.setFillWidth(false);
HBox hBox = new HBox(spacer, vBox); HBox hBox = new HBox(getSpacer(), vBox);
hBox.setFillHeight(false); hBox.setFillHeight(false);
setBottom(hBox); setBottom(hBox);
DoubleBinding spacerSize = getYAxis().widthProperty().add(getYAxis().tickLengthProperty()).add(getAxisMargin()); DoubleBinding spacerSize = getYAxis().widthProperty().add(getYAxis().tickLengthProperty()).add(getAxisMargin());
spacer.minWidthProperty().bind(spacerSize); getSpacer().minWidthProperty().bind(spacerSize);
spacer.prefWidthProperty().bind(spacerSize); getSpacer().prefWidthProperty().bind(spacerSize);
spacer.maxWidthProperty().bind(spacerSize); getSpacer().maxWidthProperty().bind(spacerSize);
}); });
createSeries(); 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 //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);
}
} }

View File

@ -35,14 +35,13 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years
Timeline.ui.ZoomRanges.fiveyears.text=Five Years Timeline.ui.ZoomRanges.fiveyears.text=Five Years
Timeline.ui.ZoomRanges.tenyears.text=Ten Years Timeline.ui.ZoomRanges.tenyears.text=Ten Years
Timeline.ui.ZoomRanges.all.text=All Timeline.ui.ZoomRanges.all.text=All
TimeLineResultView.startDateToEndDate.text={0} to {1} ViewFrame.histogramTask.title=Rebuild Histogram
VisualizationPanel.histogramTask.title=Rebuild Histogram ViewFrame.histogramTask.preparing=preparing
VisualizationPanel.histogramTask.preparing=preparing ViewFrame.histogramTask.resetUI=resetting ui
VisualizationPanel.histogramTask.resetUI=resetting ui ViewFrame.histogramTask.queryDb=querying db
VisualizationPanel.histogramTask.queryDb=querying db ViewFrame.histogramTask.updateUI2=updating ui
VisualizationPanel.histogramTask.updateUI2=updating ui ViewFrame.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings.
VisualizationPanel.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings. ViewFrame.zoomButton.text=Zoom to events
VisualizationPanel.zoomButton.text=Zoom to events
TimeZonePanel.localRadio.text=Local Time Zone TimeZonePanel.localRadio.text=Local Time Zone
TimeZonePanel.otherRadio.text=GMT / UTC TimeZonePanel.otherRadio.text=GMT / UTC
VisualizationPanel.resetFiltersButton.text=Reset all filters ViewFrame.resetFiltersButton.text=Reset all filters

View File

@ -1,24 +1,24 @@
Timeline.node.root=\u30eb\u30fc\u30c8 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.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 Timeline.ui.ZoomRanges.all.text=\u5168\u3066
TimeLineResultView.startDateToEndDate.text={0}\u304b\u3089{1}
VisualizationPanel.histogramTask.preparing=\u6e96\u5099\u4e2d ViewFrame.histogramTask.preparing=\u6e96\u5099\u4e2d
VisualizationPanel.histogramTask.queryDb=DB\u3092\u30af\u30a8\u30ea\u4e2d ViewFrame.histogramTask.queryDb=DB\u3092\u30af\u30a8\u30ea\u4e2d
VisualizationPanel.histogramTask.resetUI=ui\u3092\u518d\u8a2d\u5b9a\u4e2d ViewFrame.histogramTask.resetUI=ui\u3092\u518d\u8a2d\u5b9a\u4e2d
VisualizationPanel.histogramTask.title=\u30d2\u30b9\u30c8\u30b0\u30e9\u30e0\u3092\u518d\u30d3\u30eb\u30c9 ViewFrame.histogramTask.title=\u30d2\u30b9\u30c8\u30b0\u30e9\u30e0\u3092\u518d\u30d3\u30eb\u30c9
VisualizationPanel.histogramTask.updateUI2=ui\u3092\u66f4\u65b0\u4e2d ViewFrame.histogramTask.updateUI2=ui\u3092\u66f4\u65b0\u4e2d
TimeZonePanel.localRadio.text=\u30ed\u30fc\u30ab\u30eb\u30bf\u30a4\u30e0\u30be\u30fc\u30f3 TimeZonePanel.localRadio.text=\u30ed\u30fc\u30ab\u30eb\u30bf\u30a4\u30e0\u30be\u30fc\u30f3
VisualizationPanel.countsToggle.text=\u30ab\u30a6\u30f3\u30c8 ViewFrame.countsToggle.text=\u30ab\u30a6\u30f3\u30c8
VisualizationPanel.detailsToggle.text=\u8a73\u7d30 ViewFrame.detailsToggle.text=\u8a73\u7d30
VisualizationPanel.endLabel.text=\u30a8\u30f3\u30c9\uff1a ViewFrame.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 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
VisualizationPanel.resetFiltersButton.text=\u5168\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u30fc\u3092\u30ea\u30bb\u30c3\u30c8 ViewFrame.resetFiltersButton.text=\u5168\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u30fc\u3092\u30ea\u30bb\u30c3\u30c8
VisualizationPanel.startLabel.text=\u30b9\u30bf\u30fc\u30c8\uff1a ViewFrame.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 ViewFrame.viewModeLabel.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 ViewFrame.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.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 *=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.ClearSelectedIntervalAction.tooltTipText=\u9078\u629e\u3057\u305f\u9593\u9694\u3092\u30af\u30ea\u30a2\u3059\u308b
IntervalSelector.ZoomAction.name=\u30ba\u30fc\u30e0 IntervalSelector.ZoomAction.name=\u30ba\u30fc\u30e0
NoEventsDialog.titledPane.text=\u898b\u308c\u308b\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093 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 Timeline.ui.ZoomRanges.tenyears.text=10\u5e74
TimeLineChart.zoomHistoryActionGroup.name=\u30ba\u30fc\u30e0\u5c65\u6b74 TimeLineChart.zoomHistoryActionGroup.name=\u30ba\u30fc\u30e0\u5c65\u6b74
TimeZonePanel.title=\u6642\u9593\u3092\u6b21\u3067\u8868\u793a\uff1a TimeZonePanel.title=\u6642\u9593\u3092\u6b21\u3067\u8868\u793a\uff1a
VisualizationPanel.refresh=\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5 ViewFrame.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 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
VisualizationUpdateTask.preparing=\u30ba\u30fc\u30e0\u304a\u3088\u3073\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3092\u89e3\u6790\u4e2d ViewRefreshTask.preparing=\u30ba\u30fc\u30e0\u304a\u3088\u3073\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3092\u89e3\u6790\u4e2d

View File

@ -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);
});
}
}
}

View File

@ -23,7 +23,7 @@
<items> <items>
<HBox alignment="CENTER_LEFT" BorderPane.alignment="CENTER" HBox.hgrow="NEVER"> <HBox alignment="CENTER_LEFT" BorderPane.alignment="CENTER" HBox.hgrow="NEVER">
<children> <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> <HBox.margin>
<Insets right="5.0" /> <Insets right="5.0" />
</HBox.margin> </HBox.margin>
@ -32,7 +32,7 @@
</font> </font>
</Label> </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> <buttons>
<ToggleButton fx:id="countsToggle" alignment="TOP_LEFT" mnemonicParsing="false" selected="true"> <ToggleButton fx:id="countsToggle" alignment="TOP_LEFT" mnemonicParsing="false" selected="true">
<graphic> <graphic>
@ -58,8 +58,20 @@
<Font name="System Bold" size="16.0" /> <Font name="System Bold" size="16.0" />
</font> </font>
</ToggleButton> </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> </buttons>
</SegmentedButton> </SegmentedButton>
</children> </children>
<padding> <padding>
@ -81,7 +93,7 @@
</graphic> </graphic>
</Button> </Button>
<Separator maxWidth="1.7976931348623157E308" orientation="VERTICAL" /> <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> <graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true"> <ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image> <image>

View File

@ -29,15 +29,12 @@ import java.util.function.Supplier;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.InvalidationListener; import javafx.beans.InvalidationListener;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.MenuButton; import javafx.scene.control.MenuButton;
import javafx.scene.control.TitledPane; import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar; import javafx.scene.control.ToolBar;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
@ -60,8 +57,10 @@ import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import jfxtras.scene.control.LocalDateTimePicker; import jfxtras.scene.control.LocalDateTimePicker;
import jfxtras.scene.control.LocalDateTimeTextField; import jfxtras.scene.control.LocalDateTimeTextField;
import jfxtras.scene.control.ToggleGroupValue;
import org.controlsfx.control.NotificationPane; import org.controlsfx.control.NotificationPane;
import org.controlsfx.control.RangeSlider; import org.controlsfx.control.RangeSlider;
import org.controlsfx.control.SegmentedButton;
import org.controlsfx.control.action.Action; import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils; import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime; 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.ingest.events.DataSourceAnalysisCompletedEvent;
import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController; 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.Back;
import org.sleuthkit.autopsy.timeline.actions.ResetFilters; import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport; 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.countsview.CountsViewPane;
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane; import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree; import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
import org.sleuthkit.autopsy.timeline.ui.listvew.ListViewPane;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
/** /**
* A container for an AbstractVisualizationPane. Has a Toolbar on top to hold * A container for an AbstractTimelineView. Has a Toolbar on top to hold
* settings widgets supplied by contained AbstractVisualizationPane, and the * settings widgets supplied by contained AbstractTimelineView, and the
* histogram / time selection on bottom. * histogram / time selection on bottom.
* *
* TODO: Refactor common code out of histogram and CountsView? -jm * 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 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 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 * 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() { private final static Region NO_EVENTS_BACKGROUND = new Region() {
{ {
@ -121,7 +121,7 @@ final public class VisualizationPanel extends BorderPane {
private LoggedTask<Void> histogramTask; private LoggedTask<Void> histogramTask;
private final EventsTree eventsTree; private final EventsTree eventsTree;
private AbstractVisualizationPane<?, ?, ?, ?> visualization; private AbstractTimeLineView hostedView;
/* /*
* HBox that contains the histogram bars. * HBox that contains the histogram bars.
@ -159,12 +159,16 @@ final public class VisualizationPanel extends BorderPane {
@FXML @FXML
private ToolBar toolBar; private ToolBar toolBar;
@FXML @FXML
private Label visualizationModeLabel; private Label viewModeLabel;
@FXML
private SegmentedButton modeSegButton;
@FXML @FXML
private ToggleButton countsToggle; private ToggleButton countsToggle;
@FXML @FXML
private ToggleButton detailsToggle; private ToggleButton detailsToggle;
@FXML @FXML
private ToggleButton listToggle;
@FXML
private Button snapShotButton; private Button snapShotButton;
@FXML @FXML
private Button refreshButton; private Button refreshButton;
@ -172,7 +176,7 @@ final public class VisualizationPanel extends BorderPane {
private Button updateDBButton; 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(); private final NotificationPane notificationPane = new NotificationPane();
@ -241,25 +245,26 @@ final public class VisualizationPanel extends BorderPane {
/** /**
* Constructor * Constructor
* *
* @param controller The TimeLineController for this VisualizationPanel * @param controller The TimeLineController for this ViewFrame
* @param eventsTree The EventsTree this VisualizationPanel hosts. * @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.controller = controller;
this.filteredEvents = controller.getEventsModel(); this.filteredEvents = controller.getEventsModel();
this.eventsTree = eventsTree; this.eventsTree = eventsTree;
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS FXMLConstructor.construct(this, "ViewFrame.fxml"); // NON-NLS
} }
@FXML @FXML
@NbBundle.Messages({ @NbBundle.Messages({
"VisualizationPanel.visualizationModeLabel.text=Visualization Mode:", "ViewFrame.viewModeLabel.text=View Mode:",
"VisualizationPanel.startLabel.text=Start:", "ViewFrame.startLabel.text=Start:",
"VisualizationPanel.endLabel.text=End:", "ViewFrame.endLabel.text=End:",
"VisualizationPanel.countsToggle.text=Counts", "ViewFrame.countsToggle.text=Counts",
"VisualizationPanel.detailsToggle.text=Details", "ViewFrame.detailsToggle.text=Details",
"VisualizationPanel.zoomMenuButton.text=Zoom in/out to", "ViewFrame.listToggle.text=List",
"VisualizationPanel.tagsAddedOrDeleted=Tags have been created and/or deleted. The visualization may not be up to date." "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() { void initialize() {
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS 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); notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
setCenter(notificationPane); setCenter(notificationPane);
//configure visualization mode toggle //configure view mode toggle
visualizationModeLabel.setText(Bundle.VisualizationPanel_visualizationModeLabel_text()); viewModeLabel.setText(Bundle.ViewFrame_viewModeLabel_text());
countsToggle.setText(Bundle.VisualizationPanel_countsToggle_text()); countsToggle.setText(Bundle.ViewFrame_countsToggle_text());
detailsToggle.setText(Bundle.VisualizationPanel_detailsToggle_text()); detailsToggle.setText(Bundle.ViewFrame_detailsToggle_text());
ChangeListener<Toggle> toggleListener = (ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) -> { listToggle.setText(Bundle.ViewFrame_listToggle_text());
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);
}
};
if (countsToggle.getToggleGroup() != null) { ToggleGroupValue<ViewMode> visModeToggleGroup = new ToggleGroupValue<>();
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener); visModeToggleGroup.add(listToggle, ViewMode.LIST);
} else { visModeToggleGroup.add(detailsToggle, ViewMode.DETAIL);
countsToggle.toggleGroupProperty().addListener((Observable toggleGroup) -> { visModeToggleGroup.add(countsToggle, ViewMode.COUNTS);
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
});
}
controller.visualizationModeProperty().addListener(visualizationMode -> syncVisualizationMode()); modeSegButton.setToggleGroup(visModeToggleGroup);
syncVisualizationMode();
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 SaveSnapshotAsReport(controller, notificationPane::getContent), snapShotButton);
ActionUtils.configureButton(new UpdateDB(controller), updateDBButton); ActionUtils.configureButton(new UpdateDB(controller), updateDBButton);
/////configure start and end pickers /////configure start and end pickers
startLabel.setText(Bundle.VisualizationPanel_startLabel_text()); startLabel.setText(Bundle.ViewFrame_startLabel_text());
endLabel.setText(Bundle.VisualizationPanel_endLabel_text()); endLabel.setText(Bundle.ViewFrame_endLabel_text());
//suppress stacktraces on malformed input //suppress stacktraces on malformed input
//TODO: should we do anything else? show a warning? //TODO: should we do anything else? show a warning?
@ -326,7 +326,7 @@ final public class VisualizationPanel extends BorderPane {
rangeHistogramStack.getChildren().add(rangeSlider); 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 * rangeslider track doesn't extend to edge of node,and so the
* histrogram doesn't quite line up with the rangeslider * 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 ZoomOut(controller), zoomOutButton);
ActionUtils.configureButton(new ZoomIn(controller), zoomInButton); ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
@ -355,28 +355,28 @@ final public class VisualizationPanel extends BorderPane {
TimeLineController.getTimeZone().addListener(timeZoneProp -> refreshTimeUI()); TimeLineController.getTimeZone().addListener(timeZoneProp -> refreshTimeUI());
filteredEvents.timeRangeProperty().addListener(timeRangeProp -> refreshTimeUI()); filteredEvents.timeRangeProperty().addListener(timeRangeProp -> refreshTimeUI());
filteredEvents.zoomParametersProperty().addListener(zoomListener); filteredEvents.zoomParametersProperty().addListener(zoomListener);
refreshTimeUI(); //populate the viz refreshTimeUI(); //populate the view
refreshHistorgram(); refreshHistorgram();
} }
/** /**
* Handle TagsUpdatedEvents by marking that the visualization needs to be * Handle TagsUpdatedEvents by marking that the view needs to be
* refreshed. * 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. * filteredEventsModel's EventBus in order for this handler to be invoked.
* *
* @param event The TagsUpdatedEvent to handle. * @param event The TagsUpdatedEvent to handle.
*/ */
@Subscribe @Subscribe
public void handleTimeLineTagUpdate(TagsUpdatedEvent event) { public void handleTimeLineTagUpdate(TagsUpdatedEvent event) {
visualization.setOutOfDate(); hostedView.setOutOfDate();
Platform.runLater(() -> { Platform.runLater(() -> {
if (notificationPane.isShowing() == false) { if (notificationPane.isShowing() == false) {
notificationPane.getActions().setAll(new Refresh()); 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 * Handle a RefreshRequestedEvent from the events model by clearing the
* refresh notification. * 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. * filteredEventsModel's EventBus in order for this handler to be invoked.
* *
* @param event The RefreshRequestedEvent to handle. * @param event The RefreshRequestedEvent to handle.
@ -393,7 +393,7 @@ final public class VisualizationPanel extends BorderPane {
@Subscribe @Subscribe
public void handleRefreshRequested(RefreshRequestedEvent event) { public void handleRefreshRequested(RefreshRequestedEvent event) {
Platform.runLater(() -> { Platform.runLater(() -> {
if (Bundle.VisualizationPanel_tagsAddedOrDeleted().equals(notificationPane.getText())) { if (Bundle.ViewFrame_tagsAddedOrDeleted().equals(notificationPane.getText())) {
notificationPane.hide(); notificationPane.hide();
} }
}); });
@ -401,16 +401,16 @@ final public class VisualizationPanel extends BorderPane {
/** /**
* Handle a DBUpdatedEvent from the events model by refreshing the * 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. * filteredEventsModel's EventBus in order for this handler to be invoked.
* *
* @param event The DBUpdatedEvent to handle. * @param event The DBUpdatedEvent to handle.
*/ */
@Subscribe @Subscribe
public void handleDBUpdated(DBUpdatedEvent event) { public void handleDBUpdated(DBUpdatedEvent event) {
visualization.refresh(); hostedView.refresh();
refreshHistorgram(); refreshHistorgram();
Platform.runLater(notificationPane::hide); Platform.runLater(notificationPane::hide);
} }
@ -419,7 +419,7 @@ final public class VisualizationPanel extends BorderPane {
* Handle a DataSourceAddedEvent from the events model by showing a * Handle a DataSourceAddedEvent from the events model by showing a
* notification. * 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. * filteredEventsModel's EventBus in order for this handler to be invoked.
* *
* @param event The DataSourceAddedEvent to handle. * @param event The DataSourceAddedEvent to handle.
@ -427,11 +427,11 @@ final public class VisualizationPanel extends BorderPane {
@Subscribe @Subscribe
@NbBundle.Messages({ @NbBundle.Messages({
"# {0} - datasource name", "# {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) { public void handlDataSourceAdded(DataSourceAddedEvent event) {
Platform.runLater(() -> { Platform.runLater(() -> {
notificationPane.getActions().setAll(new UpdateDB(controller)); 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 * Handle a DataSourceAnalysisCompletedEvent from the events modelby showing
* a notification. * 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. * filteredEventsModel's EventBus in order for this handler to be invoked.
* *
* @param event The DataSourceAnalysisCompletedEvent to handle. * @param event The DataSourceAnalysisCompletedEvent to handle.
@ -447,11 +447,11 @@ final public class VisualizationPanel extends BorderPane {
@Subscribe @Subscribe
@NbBundle.Messages({ @NbBundle.Messages({
"# {0} - datasource name", "# {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) { public void handleAnalysisCompleted(DataSourceAnalysisCompletedEvent event) {
Platform.runLater(() -> { Platform.runLater(() -> {
notificationPane.getActions().setAll(new UpdateDB(controller)); 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>( 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(); private final Lighting lighting = new Lighting();
@Override @Override
protected Void call() throws Exception { 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; long max = 0;
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval()); 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 //clear old data, and reset ranges and series
Platform.runLater(() -> { 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; 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 //query for current range
long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum(); long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum();
bins.add(count); bins.add(count);
@ -510,7 +510,7 @@ final public class VisualizationPanel extends BorderPane {
final double fMax = Math.log(max); final double fMax = Math.log(max);
final ArrayList<Long> fbins = new ArrayList<>(bins); final ArrayList<Long> fbins = new ArrayList<>(bins);
Platform.runLater(() -> { 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(); histogramBox.getChildren().clear();
@ -576,18 +576,28 @@ final public class VisualizationPanel extends BorderPane {
} }
/** /**
* Switch to the given VisualizationMode, by swapping out the hosted * Switch to the given ViewMode, by swapping out the hosted
* AbstractVislualization for one of the correct type. * AbstractTimelineView for one of the correct type.
*/ */
private void syncVisualizationMode() { private void syncViewMode() {
AbstractVisualizationPane<?, ?, ?, ?> vizPane; AbstractTimeLineView view;
VisualizationMode visMode = controller.visualizationModeProperty().get(); ViewMode viewMode = controller.viewModeProperty().get();
//make new visualization. //make new view.
switch (visMode) { switch (viewMode) {
case LIST:
view = new ListViewPane(controller);
Platform.runLater(() -> {
listToggle.setSelected(true);
//TODO: should remove listeners from events tree
});
break;
case COUNTS: case COUNTS:
vizPane = new CountsViewPane(controller); view = new CountsViewPane(controller);
Platform.runLater(() -> countsToggle.setSelected(true)); Platform.runLater(() -> {
countsToggle.setSelected(true);
//TODO: should remove listeners from events tree
});
break; break;
case DETAIL: case DETAIL:
DetailViewPane detailViewPane = new DetailViewPane(controller); DetailViewPane detailViewPane = new DetailViewPane(controller);
@ -596,34 +606,34 @@ final public class VisualizationPanel extends BorderPane {
detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents()); detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents());
eventsTree.setDetailViewPane(detailViewPane); eventsTree.setDetailViewPane(detailViewPane);
}); });
vizPane = detailViewPane; view = detailViewPane;
break; break;
default: 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(() -> { Platform.runLater(() -> {
//clear out old vis. //clear out old view.
if (visualization != null) { if (hostedView != null) {
toolBar.getItems().removeAll(visualization.getSettingsNodes()); toolBar.getItems().removeAll(hostedView.getSettingsNodes());
visualization.dispose(); hostedView.dispose();
} }
visualization = vizPane; hostedView = view;
//setup new vis. //setup new view.
ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new visualization ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view
visualization.refresh(); hostedView.refresh();
toolBar.getItems().addAll(2, vizPane.getSettingsNodes()); toolBar.getItems().addAll(2, view.getSettingsNodes());
notificationPane.setContent(visualization); notificationPane.setContent(hostedView);
//listen to has events property and show "dialog" if it is false. //listen to has events property and show "dialog" if it is false.
visualization.hasVisibleEventsProperty().addListener(hasEvents -> { hostedView.hasVisibleEventsProperty().addListener(hasEvents -> {
notificationPane.setContent(visualization.hasVisibleEvents() notificationPane.setContent(hostedView.hasVisibleEvents()
? visualization ? hostedView
: new StackPane(visualization, : new StackPane(hostedView,
NO_EVENTS_BACKGROUND, 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 assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
titledPane.setText(Bundle.NoEventsDialog_titledPane_text()); 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()); dismissButton.setOnAction(actionEvent -> closeCallback.run());
@ -689,7 +699,7 @@ final public class VisualizationPanel extends BorderPane {
LocalDateTime pickerTime = pickerSupplier.get().getLocalDateTime(); LocalDateTime pickerTime = pickerSupplier.get().getLocalDateTime();
if (pickerTime != null) { if (pickerTime != null) {
controller.pushTimeRange(intervalMapper.apply(filteredEvents.timeRangeProperty().get(), localDateTimeToEpochMilli(pickerTime))); 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 { private class Refresh extends Action {
@NbBundle.Messages({ @NbBundle.Messages({
"VisualizationPanel.refresh.text=Refresh Vis.", "ViewFrame.refresh.text=Refresh View",
"VisualizationPanel.refresh.longText=Refresh the visualization to include information that is in the DB but not visualized, such as newly updated tags."}) "ViewFrame.refresh.longText=Refresh the view to include information that is in the DB but not displayed, such as newly updated tags."})
Refresh() { Refresh() {
super(Bundle.VisualizationPanel_refresh_text()); super(Bundle.ViewFrame_refresh_text());
setLongText(Bundle.VisualizationPanel_refresh_longText()); setLongText(Bundle.ViewFrame_refresh_longText());
setGraphic(new ImageView(REFRESH)); setGraphic(new ImageView(REFRESH));
setEventHandler(actionEvent -> filteredEvents.postRefreshRequest()); setEventHandler(actionEvent -> filteredEvents.postRefreshRequest());
disabledProperty().bind(visualization.outOfDateProperty().not()); disabledProperty().bind(hostedView.outOfDateProperty().not());
} }
} }
} }

View File

@ -58,7 +58,7 @@ import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; 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; 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 * Platform.runLater(java.lang.Runnable). The FilteredEventsModel should
* encapsulate all need synchronization internally. * 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()); private static final Logger LOGGER = Logger.getLogger(CountsViewPane.class.getName());
@ -105,13 +105,15 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
/** /**
* Constructor * Constructor
* *
* @param controller The TimelineController for this visualization. * @param controller The TimelineController for this view.
*/ */
@NbBundle.Messages({ @NbBundle.Messages({
"# {0} - scale name", "# {0} - scale name",
"CountsViewPane.numberOfEvents=Number of Events ({0})"}) "CountsViewPane.numberOfEvents=Number of Events ({0})"})
public CountsViewPane(TimeLineController controller) { public CountsViewPane(TimeLineController controller) {
super(controller); super(controller);
setChart(new EventCountsChart(controller, dateAxis, countAxis, getSelectedNodes())); setChart(new EventCountsChart(controller, dateAxis, countAxis, getSelectedNodes()));
getChart().setData(dataSeries); getChart().setData(dataSeries);
Tooltip.install(getChart(), getDefaultTooltip()); Tooltip.install(getChart(), getDefaultTooltip());
@ -156,7 +158,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@Override @Override
protected void clearChartData() { protected void clearData() {
for (XYChart.Series<String, Number> series : dataSeries) { for (XYChart.Series<String, Number> series : dataSeries) {
series.getData().clear(); series.getData().clear();
} }
@ -247,7 +249,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
"CountsViewPane.scaleLabel.text=Scale:", "CountsViewPane.scaleLabel.text=Scale:",
"CountsViewPane.scaleHelp.label.text=Scales: ", "CountsViewPane.scaleHelp.label.text=Scales: ",
"CountsViewPane.linearRadio.text=Linear", "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.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.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."}) "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({ @NbBundle.Messages({
"CountsViewPane.loggedTask.name=Updating Counts View", "CountsViewPane.loggedTask.name=Updating Counts View",
"CountsViewPane.loggedTask.updatingCounts=Populating visualization"}) "CountsViewPane.loggedTask.updatingCounts=Populating view"})
private class CountsUpdateTask extends VisualizationRefreshTask<List<String>> { private class CountsUpdateTask extends ViewRefreshTask<List<String>> {
CountsUpdateTask() { CountsUpdateTask() {
super(Bundle.CountsViewPane_loggedTask_name(), true); super(Bundle.CountsViewPane_loggedTask_name(), true);
} }
@Override
protected void succeeded() {
super.succeeded();
layoutDateLabels();
}
@Override @Override
protected Boolean call() throws Exception { protected Boolean call() throws Exception {
super.call(); super.call();
@ -354,7 +362,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
List<Interval> intervals = rangeInfo.getIntervals(); List<Interval> intervals = rangeInfo.getIntervals();
//clear old data, and reset ranges and series //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()); updateMessage(Bundle.CountsViewPane_loggedTask_updatingCounts());
int chartMax = 0; int chartMax = 0;
@ -412,7 +420,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
} }
@Override @Override
protected void setDateAxisValues(List<String> categories) { protected void setDateValues(List<String> categories) {
dateAxis.getCategories().setAll(categories); dateAxis.getCategories().setAll(categories);
} }
} }

View File

@ -47,7 +47,7 @@ import org.joda.time.Seconds;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ColorUtilities; import org.sleuthkit.autopsy.coreutils.ColorUtilities;
import org.sleuthkit.autopsy.timeline.TimeLineController; 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.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType; 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_detailSwitchMessage(),
Bundle.CountsViewPane_detailSwitchTitle(), JOptionPane.YES_NO_OPTION); Bundle.CountsViewPane_detailSwitchTitle(), JOptionPane.YES_NO_OPTION);
if (showConfirmDialog == JOptionPane.YES_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); }
*/
} }
} }
} }

View File

@ -54,7 +54,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe; import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; 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.utils.MappedList;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; 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 * grouped EventStripes, etc, etc. The leaves of the trees are EventClusters or
* SingleEvents. * 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()); 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 * 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; private ZoomParams currentZoomParams;
@ -204,7 +204,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@Override @Override
protected void clearChartData() { protected void clearData() {
getChart().reset(); getChart().reset();
} }
@ -347,13 +347,12 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
@NbBundle.Messages({ @NbBundle.Messages({
"DetailViewPane.loggedTask.queryDb=Retreiving event data", "DetailViewPane.loggedTask.queryDb=Retreiving event data",
"DetailViewPane.loggedTask.name=Updating Details View", "DetailViewPane.loggedTask.name=Updating Details View",
"DetailViewPane.loggedTask.updateUI=Populating visualization", "DetailViewPane.loggedTask.updateUI=Populating view",
"DetailViewPane.loggedTask.continueButton=Continue", "DetailViewPane.loggedTask.continueButton=Continue",
"DetailViewPane.loggedTask.backButton=Back (Cancel)", "DetailViewPane.loggedTask.backButton=Back (Cancel)",
"# {0} - number of events", "# {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?"}) "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() { DetailsUpdateTask() {
super(Bundle.DetailViewPane_loggedTask_name(), true); super(Bundle.DetailViewPane_loggedTask_name(), true);
@ -409,7 +408,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
currentZoomParams = newZoomParams; currentZoomParams = newZoomParams;
//clear the chart and set the horixontal axis //clear the chart and set the horixontal axis
resetChart(eventsModel.getTimeRange()); resetView(eventsModel.getTimeRange());
updateMessage(Bundle.DetailViewPane_loggedTask_updateUI()); updateMessage(Bundle.DetailViewPane_loggedTask_updateUI());
@ -433,9 +432,15 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
} }
@Override @Override
protected void setDateAxisValues(Interval timeRange) { protected void setDateValues(Interval timeRange) {
detailsChartDateAxis.setRange(timeRange, true); detailsChartDateAxis.setRange(timeRange, true);
pinnedDateAxis.setRange(timeRange, true); pinnedDateAxis.setRange(timeRange, true);
} }
@Override
protected void succeeded() {
super.succeeded();
layoutDateLabels();
}
} }
} }

View File

@ -432,8 +432,8 @@ final class DetailsChart extends Control implements TimeLineChart<DateTime> {
configureMouseListeners(pinnedLane, mouseClickedHandler, chartDragHandler); configureMouseListeners(pinnedLane, mouseClickedHandler, chartDragHandler);
//show and hide pinned lane in response to settings property change //show and hide pinned lane in response to settings property change
getSkinnable().getLayoutSettings().pinnedLaneShowing().addListener(observable -> syncPinnedShowing()); getSkinnable().getLayoutSettings().pinnedLaneShowing().addListener(observable -> syncPinnedLaneShowing());
syncPinnedShowing(); syncPinnedLaneShowing();
//show and remove interval selector in sync with control state change //show and remove interval selector in sync with control state change
getSkinnable().intervalSelectorProp.addListener((observable, oldIntervalSelector, newIntervalSelector) -> { 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 * Show the pinned lane if and only if the settings object says it
* should be. * should be.
*/ */
private void syncPinnedShowing() { private void syncPinnedLaneShowing() {
boolean pinnedLaneShowing = getSkinnable().getLayoutSettings().isPinnedLaneShowing(); boolean pinnedLaneShowing = getSkinnable().getLayoutSettings().isPinnedLaneShowing();
if (pinnedLaneShowing == false) { if (pinnedLaneShowing == false) {
//Save the divider position for later. //Save the divider position for later.

View File

@ -58,11 +58,11 @@ import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter; import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter; 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; 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. * layout code.
* *
* NOTE: It was too hard to control the threading of this chart via the * 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 //add a dummy series or the chart is never rendered
setData(FXCollections.observableList(Arrays.asList(new Series<DateTime, Y>()))); setData(FXCollections.observableList(Arrays.asList(new Series<DateTime, Y>())));
Tooltip.install(this, AbstractVisualizationPane.getDefaultTooltip()); Tooltip.install(this, AbstractTimelineChart.getDefaultTooltip());
dateAxis.setAutoRanging(false); dateAxis.setAutoRanging(false);
setLegendVisible(false); setLegendVisible(false);

View File

@ -77,7 +77,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.events.TagsAddedEvent; import org.sleuthkit.autopsy.timeline.events.TagsAddedEvent;
import org.sleuthkit.autopsy.timeline.events.TagsDeletedEvent; 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 org.sleuthkit.autopsy.timeline.ui.ContextMenuProvider;
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventNodeBase.show; import static org.sleuthkit.autopsy.timeline.ui.detailview.EventNodeBase.show;
import static org.sleuthkit.autopsy.timeline.ui.detailview.MultiEventNodeBase.CORNER_RADII_3; 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 //set up mouse hover effect and tooltip
setOnMouseEntered(mouseEntered -> { setOnMouseEntered(mouseEntered -> {
Tooltip.uninstall(chartLane, AbstractVisualizationPane.getDefaultTooltip()); Tooltip.uninstall(chartLane, AbstractTimelineChart.getDefaultTooltip());
showHoverControls(true); showHoverControls(true);
toFront(); toFront();
}); });
@ -176,7 +176,7 @@ public abstract class EventNodeBase<Type extends TimeLineEvent> extends StackPan
if (parentNode != null) { if (parentNode != null) {
parentNode.showHoverControls(true); parentNode.showHoverControls(true);
} else { } else {
Tooltip.install(chartLane, AbstractVisualizationPane.getDefaultTooltip()); Tooltip.install(chartLane, AbstractTimelineChart.getDefaultTooltip());
} }
}); });
setOnMouseClicked(new ClickHandler()); setOnMouseClicked(new ClickHandler());

View File

@ -25,7 +25,7 @@ import javafx.scene.shape.Line;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController; 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 * 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."}) "GuideLine.tooltip.text={0}\nRight-click to remove.\nDrag to reposition."})
class GuideLine extends Line { 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 Tooltip tooltip = new Tooltip();
private final DetailsChart chart; private final DetailsChart chart;

View File

@ -41,7 +41,6 @@ import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.ResetFilters; import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter; import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
@ -129,20 +128,29 @@ final public class FilterSetPanel extends BorderPane {
hiddenDescriptionsListView.setItems(controller.getQuickHideFilters()); hiddenDescriptionsListView.setItems(controller.getQuickHideFilters());
hiddenDescriptionsListView.setCellFactory(listView -> getNewDiscriptionFilterListCell()); 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(); applyFilters();
if (controller.visualizationModeProperty().get() == VisualizationMode.COUNTS) { switch (controller.getViewMode()) {
dividerPosition = splitPane.getDividerPositions()[0]; case COUNTS:
splitPane.setDividerPositions(1); case LIST:
hiddenDescriptionsPane.setExpanded(false); //hide for counts and lists, but remember divider position
hiddenDescriptionsPane.setCollapsible(false); dividerPosition = splitPane.getDividerPositions()[0];
hiddenDescriptionsPane.setDisable(true); splitPane.setDividerPositions(1);
} else { hiddenDescriptionsPane.setExpanded(false);
splitPane.setDividerPositions(dividerPosition); hiddenDescriptionsPane.setCollapsible(false);
hiddenDescriptionsPane.setDisable(false); hiddenDescriptionsPane.setDisable(true);
hiddenDescriptionsPane.setCollapsible(true); break;
hiddenDescriptionsPane.setExpanded(true); case DETAIL:
hiddenDescriptionsPane.setCollapsible(false); //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());
} }
}); });
} }

View File

@ -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>

View File

@ -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();
});
}
});
}
}
}
}

View File

@ -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) {
}
}
}

View File

@ -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());
}
}
}
}
}

View File

@ -22,6 +22,7 @@ import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.InvalidationListener; import javafx.beans.InvalidationListener;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Label; import javafx.scene.control.Label;
@ -31,7 +32,7 @@ import javafx.util.StringConverter;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController; 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.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
@ -102,15 +103,14 @@ public class ZoomSettingsPane extends TitledPane {
Function.identity()); Function.identity());
descrLODLabel.setText(Bundle.ZoomSettingsPane_descrLODLabel_text()); descrLODLabel.setText(Bundle.ZoomSettingsPane_descrLODLabel_text());
//the description slider is only usefull in the detail view //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 * 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 * to the amount of time used as units along the x-axis of the view, and
* visualization, and since we don't want to show "forever" as a time * since we don't want to show "forever" as a time unit, the range of
* unit, the range of the slider is restricted, and there is an offset * the slider is restricted, and there is an offset of 1 between the
* of 1 between the "real" value, and what is shown in the slider * "real" value, and what is shown in the slider labels.
* labels.
*/ */
timeUnitSlider.setMax(TimeUnits.values().length - 2); timeUnitSlider.setMax(TimeUnits.values().length - 2);
configureSliderListeners(timeUnitSlider, configureSliderListeners(timeUnitSlider,
@ -121,6 +121,12 @@ public class ZoomSettingsPane extends TitledPane {
modelTimeRange -> RangeDivisionInfo.getRangeDivisionInfo(modelTimeRange).getPeriodSize().ordinal() - 1, 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 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()); 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 //set the tick labels to the enum displayNames
slider.setLabelFormatter(new EnumSliderLabelFormatter<>(enumClass, labelIndexMapper)); 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 -> { final InvalidationListener sliderListener = observable -> {
//only process event if the slider value is not changing (user has released slider thumb) //only process event if the slider value is not changing (user has released slider thumb)
if (slider.isValueChanging() == false) { if (slider.isValueChanging() == false) {

View File

@ -18,7 +18,6 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.actions; package org.sleuthkit.autopsy.imagegallery.actions;
import javafx.event.ActionEvent;
import javafx.scene.control.Menu; import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javax.swing.JMenu; import javax.swing.JMenu;
@ -34,9 +33,7 @@ public class SwingMenuItemAdapter extends MenuItem {
SwingMenuItemAdapter(final JMenuItem jMenuItem) { SwingMenuItemAdapter(final JMenuItem jMenuItem) {
super(jMenuItem.getText()); super(jMenuItem.getText());
this.jMenuItem = jMenuItem; this.jMenuItem = jMenuItem;
setOnAction((ActionEvent t) -> { setOnAction(actionEvent -> jMenuItem.doClick());
jMenuItem.doClick();
});
} }
public static MenuItem create(MenuElement jmenuItem) { public static MenuItem create(MenuElement jmenuItem) {