diff --git a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties
index d23cfbb773..b417dc2072 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties
@@ -24,4 +24,4 @@ TimelinePanel.jButton7.text=3d
TimelinePanel.jButton2.text=1m
TimelinePanel.jButton3.text=3m
TimelinePanel.jButton4.text=2w
-ProgressWindow.progressHeader.text=\
\ No newline at end of file
+ProgressWindow.progressHeader.text=\
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties
index ba1ee4f94a..3bd24f9ef1 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties
+++ b/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties
@@ -1,48 +1,49 @@
-CTL_MakeTimeline=\u300C\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u300D
-CTL_TimeLineTopComponent=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A6\u30A3\u30F3\u30C9\u30A6
-CTL_TimeLineTopComponentAction=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C8\u30C3\u30D7\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8
-HINT_TimeLineTopComponent=\u3053\u308C\u306F\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A6\u30A3\u30F3\u30C9\u30A6\u3067\u3059
-OpenTimelineAction.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3
-Timeline.frameName.text={0} - Autopsy\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3
-Timeline.goToButton.text=\u4E0B\u8A18\u3078\u79FB\u52D5\uFF1A
-Timeline.node.root=\u30EB\u30FC\u30C8
-Timeline.pushDescrLOD.confdlg.msg={0}\u30A4\u30D9\u30F3\u30C8\u306E\u8A73\u7D30\u304C\u8868\u793A\u53EF\u80FD\u3067\u3059\u3002\u3053\u306E\u51E6\u7406\u306F\u9577\u6642\u9593\u304B\u304B\u308B\u3082\u3057\u304F\u306FAutopsy\u3092\u30AF\u30E9\u30C3\u30B7\u30E5\u3059\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\n\n\u5B9F\u884C\u3057\u307E\u3059\u304B\uFF1F
-Timeline.resultPanel.loading=\u30ED\u30FC\u30C9\u4E2D\u30FB\u30FB\u30FB
-Timeline.resultsPanel.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u7D50\u679C
-Timeline.runJavaFxThread.progress.creating=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u4F5C\u6210\u4E2D\u30FB\u30FB\u30FB
-Timeline.zoomOutButton.text=\u30BA\u30FC\u30E0\u30A2\u30A6\u30C8
-TimelineFrame.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3
-TimeLineTopComponent.eventsTab.name=\u30A4\u30D9\u30F3\u30C8
-TimeLineTopComponent.filterTab.name=\u30D5\u30A3\u30EB\u30BF\u30FC
-OpenTimeLineAction.msgdlg.text=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u304C\u3042\u308A\u307E\u305B\u3093\u3002
-PrompDialogManager.buttonType.continueNoUpdate=\u66F4\u65B0\u305B\u305A\u6B21\u3078
-PrompDialogManager.buttonType.showTimeline=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u8868\u793A
-PrompDialogManager.buttonType.update=\u66F4\u65B0
-PromptDialogManager.confirmDuringIngest.contentText=\u6B21\u3078\u9032\u307F\u307E\u3059\u304B\uFF1F
-PromptDialogManager.confirmDuringIngest.headerText=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u304C\u5B8C\u4E86\u3059\u308B\u524D\u306B\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u8868\u793A\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u307E\u3059\u3002\n\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u304C\u5B8C\u6210\u3057\u3066\u3044\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002
-PromptDialogManager.progressDialog.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u3092\u5165\u529B\u4E2D
-PromptDialogManager.rebuildPrompt.details=\u8A73\u7D30\uFF1A
-PromptDialogManager.rebuildPrompt.headerText=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u304C\u4E0D\u5B8C\u5168\u307E\u305F\u306F\u6700\u65B0\u3067\u306F\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\n \u6B20\u843D\u3057\u3066\u3044\u308B\u307E\u305F\u306F\u4E0D\u6B63\u78BA\u306A\u30A4\u30D9\u30F3\u30C8\u304C\u4E00\u90E8\u3042\u308B\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\u4E00\u90E8\u306E\u6A5F\u80FD\u304C\u5229\u7528\u3067\u304D\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002
-Timeline.confirmation.dialogs.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u66F4\u65B0\u3057\u307E\u3059\u304B\uFF1F
-Timeline.pushDescrLOD.confdlg.title=\u8AAC\u660E\u306E\u8A18\u8FF0\u30EC\u30D9\u30EB\u3092\u5909\u66F4\u3057\u307E\u3059\u304B\uFF1F
-TimeLineController.errorTitle=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A8\u30E9\u30FC
-TimeLineController.outOfDate.errorMessage=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u304C\u6700\u65B0\u304B\u78BA\u8A8D\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u66F4\u65B0\u304C\u5FC5\u8981\u3060\u3068\u60F3\u5B9A\u3057\u307E\u3059\u3002
-TimeLineController.rebuildReasons.incompleteOldSchema=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A4\u30D9\u30F3\u30C8\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306B\u4E0D\u5B8C\u5168\u306A\u60C5\u5831\u304C\u4EE5\u524D\u5165\u529B\u3055\u308C\u3066\u3044\u307E\u3057\u305F\uFF1A\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u66F4\u65B0\u3057\u306A\u3044\u3068\u3001\u4E00\u90E8\u306E\u6A5F\u80FD\u304C\u5229\u7528\u3067\u304D\u306A\u3044\u3001\u307E\u305F\u306F\u6A5F\u80FD\u3057\u306A\u3044\u304B\u3082\u3057\u308C\u306A\u3044\u3067\u3059\u3002
-TimeLineController.rebuildReasons.ingestWasRunning=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u304C\u5B9F\u884C\u4E2D\u306B\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A4\u30D9\u30F3\u30C8\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306B\u60C5\u5831\u304C\u5165\u529B\u3055\u308C\u3066\u3044\u307E\u3057\u305F\uFF1A\u30A4\u30D9\u30F3\u30C8\u304C\u6B20\u3051\u3066\u3044\u308B\u3001\u4E0D\u5B8C\u5168\u3001\u307E\u305F\u306F\u4E0D\u6B63\u78BA\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002
-TimeLineController.rebuildReasons.outOfDate=\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u304C\u6700\u65B0\u3067\u306F\u3042\u308A\u307E\u305B\u3093\uFF1A\u898B\u308C\u306A\u3044\u30A4\u30D9\u30F3\u30C8\u304C\u3042\u308A\u307E\u3059\u3002
-TimeLineController.rebuildReasons.outOfDateError=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u304C\u6700\u65B0\u304B\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002
-TimeLinecontroller.updateNowQuestion=\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u4ECA\u66F4\u65B0\u3057\u307E\u3059\u304B\uFF1F
+CTL_MakeTimeline=\u300c\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u300d
+CTL_TimeLineTopComponent=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a6\u30a3\u30f3\u30c9\u30a6
+CTL_TimeLineTopComponentAction=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c8\u30c3\u30d7\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8
+HINT_TimeLineTopComponent=\u3053\u308c\u306f\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a6\u30a3\u30f3\u30c9\u30a6\u3067\u3059
+OpenTimelineAction.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3
+Timeline.frameName.text={0} - Autopsy\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3
+Timeline.goToButton.text=\u4e0b\u8a18\u3078\u79fb\u52d5\uff1a
+Timeline.node.root=\u30eb\u30fc\u30c8
+Timeline.pushDescrLOD.confdlg.msg={0}\u30a4\u30d9\u30f3\u30c8\u306e\u8a73\u7d30\u304c\u8868\u793a\u53ef\u80fd\u3067\u3059\u3002\u3053\u306e\u51e6\u7406\u306f\u9577\u6642\u9593\u304b\u304b\u308b\u3082\u3057\u304f\u306fAutopsy\u3092\u30af\u30e9\u30c3\u30b7\u30e5\u3059\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002\n\n\u5b9f\u884c\u3057\u307e\u3059\u304b\uff1f
+Timeline.resultPanel.loading=\u30ed\u30fc\u30c9\u4e2d\u30fb\u30fb\u30fb
+Timeline.resultsPanel.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u7d50\u679c
+Timeline.runJavaFxThread.progress.creating=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u4f5c\u6210\u4e2d\u30fb\u30fb\u30fb
+Timeline.zoomOutButton.text=\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8
+TimelineFrame.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3
+TimeLineTopComponent.eventsTab.name=\u30a4\u30d9\u30f3\u30c8
+TimeLineTopComponent.filterTab.name=\u30d5\u30a3\u30eb\u30bf\u30fc
+OpenTimeLineAction.msgdlg.text=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002
+PrompDialogManager.buttonType.continueNoUpdate=\u66f4\u65b0\u305b\u305a\u6b21\u3078
+PrompDialogManager.buttonType.showTimeline=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u8868\u793a
+PrompDialogManager.buttonType.update=\u66f4\u65b0
+PromptDialogManager.confirmDuringIngest.contentText=\u6b21\u3078\u9032\u307f\u307e\u3059\u304b\uff1f
+PromptDialogManager.confirmDuringIngest.headerText=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b8c\u4e86\u3059\u308b\u524d\u306b\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u8868\u793a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u307e\u3059\u3002\n\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u304c\u5b8c\u6210\u3057\u3066\u3044\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
+PromptDialogManager.progressDialog.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u3092\u5165\u529b\u4e2d
+PromptDialogManager.rebuildPrompt.details=\u8a73\u7d30\uff1a
+PromptDialogManager.rebuildPrompt.headerText=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u4e0d\u5b8c\u5168\u307e\u305f\u306f\u6700\u65b0\u3067\u306f\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002\n \u6b20\u843d\u3057\u3066\u3044\u308b\u307e\u305f\u306f\u4e0d\u6b63\u78ba\u306a\u30a4\u30d9\u30f3\u30c8\u304c\u4e00\u90e8\u3042\u308b\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002\u4e00\u90e8\u306e\u6a5f\u80fd\u304c\u5229\u7528\u3067\u304d\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
+Timeline.confirmation.dialogs.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f
+Timeline.pushDescrLOD.confdlg.title=\u8aac\u660e\u306e\u8a18\u8ff0\u30ec\u30d9\u30eb\u3092\u5909\u66f4\u3057\u307e\u3059\u304b\uff1f
+TimeLineController.errorTitle=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a8\u30e9\u30fc
+TimeLineController.outOfDate.errorMessage=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u304c\u6700\u65b0\u304b\u78ba\u8a8d\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u66f4\u65b0\u304c\u5fc5\u8981\u3060\u3068\u60f3\u5b9a\u3057\u307e\u3059\u3002
+TimeLineController.rebuildReasons.incompleteOldSchema=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a4\u30d9\u30f3\u30c8\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u4e0d\u5b8c\u5168\u306a\u60c5\u5831\u304c\u4ee5\u524d\u5165\u529b\u3055\u308c\u3066\u3044\u307e\u3057\u305f\uff1a\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u66f4\u65b0\u3057\u306a\u3044\u3068\u3001\u4e00\u90e8\u306e\u6a5f\u80fd\u304c\u5229\u7528\u3067\u304d\u306a\u3044\u3001\u307e\u305f\u306f\u6a5f\u80fd\u3057\u306a\u3044\u304b\u3082\u3057\u308c\u306a\u3044\u3067\u3059\u3002
+TimeLineController.rebuildReasons.ingestWasRunning=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b9f\u884c\u4e2d\u306b\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a4\u30d9\u30f3\u30c8\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u60c5\u5831\u304c\u5165\u529b\u3055\u308c\u3066\u3044\u307e\u3057\u305f\uff1a\u30a4\u30d9\u30f3\u30c8\u304c\u6b20\u3051\u3066\u3044\u308b\u3001\u4e0d\u5b8c\u5168\u3001\u307e\u305f\u306f\u4e0d\u6b63\u78ba\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
+TimeLineController.rebuildReasons.outOfDate=\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u304c\u6700\u65b0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\uff1a\u898b\u308c\u306a\u3044\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u3059\u3002
+TimeLineController.rebuildReasons.outOfDateError=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u304c\u6700\u65b0\u304b\u78ba\u8a8d\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
+TimeLinecontroller.updateNowQuestion=\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u4eca\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f
TimelinePanel.jButton13.text=\u5168\u3066
-Timeline.yearBarChart.x.years=\u5E74
-TimelinePanel.jButton1.text=6\u30F6\u6708
+Timeline.yearBarChart.x.years=\u5e74
+TimelinePanel.jButton1.text=6\u30f6\u6708
TimelinePanel.jButton10.text=1\u6642\u9593
TimelinePanel.jButton9.text=12\u6642\u9593
-TimelinePanel.jButton11.text=5\u5E74
-TimelinePanel.jButton12.text=10\u5E74
+TimelinePanel.jButton11.text=5\u5e74
+TimelinePanel.jButton12.text=10\u5e74
TimelinePanel.jButton6.text=1\u9031\u9593
-TimelinePanel.jButton5.text=1\u5E74
-TimelinePanel.jButton8.text=1\u65E5
-TimelinePanel.jButton7.text=3\u65E5
-TimelinePanel.jButton2.text=1\u30F6\u6708
-TimelinePanel.jButton3.text=3\u30F6\u6708
-TimelinePanel.jButton4.text=2\u9031\u9593
\ No newline at end of file
+TimelinePanel.jButton5.text=1\u5e74
+TimelinePanel.jButton8.text=1\u65e5
+TimelinePanel.jButton7.text=3\u65e5
+TimelinePanel.jButton2.text=1\u30f6\u6708
+TimelinePanel.jButton3.text=3\u30f6\u6708
+TimelinePanel.jButton4.text=2\u9031\u9593
+TimeLineResultView.startDateToEndDate.text={0}\u304b\u3089{1}
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form
index 1f0f367115..a0ca021ff5 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form
+++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form
@@ -82,33 +82,45 @@
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java
index 2a37c6ef1b..1e616fb735 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2013-16 Basis Technology Corp.
+ * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,15 +19,15 @@
package org.sleuthkit.autopsy.timeline;
import com.google.common.collect.Iterables;
-import java.awt.BorderLayout;
import java.beans.PropertyVetoException;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
-import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
@@ -40,6 +40,7 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javax.swing.SwingUtilities;
import org.controlsfx.control.Notifications;
+import org.joda.time.format.DateTimeFormatter;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.nodes.AbstractNode;
@@ -54,12 +55,13 @@ import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.corecomponents.DataContentPanel;
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
import org.sleuthkit.autopsy.timeline.explorernodes.EventNode;
+import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
import org.sleuthkit.autopsy.timeline.ui.HistoryToolBar;
import org.sleuthkit.autopsy.timeline.ui.StatusBar;
-import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView;
import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
@@ -80,55 +82,31 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName());
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private final DataContentPanel contentViewerPanel;
- private final TimeLineResultView tlResultView;
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+ private DataResultPanel dataResultPanel;
+ @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private final ExplorerManager em = new ExplorerManager();
+
private final TimeLineController controller;
+ private Set selectedEventIDs = new HashSet<>();
+
@NbBundle.Messages({
"TimelineTopComponent.selectedEventListener.errorMsg=There was a problem getting the content for the selected event."})
/**
* Listener that drives the ContentViewer when in List ViewMode.
*/
- private final InvalidationListener selectedEventListener = new InvalidationListener() {
+ private final InvalidationListener selectedEventsListener = new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
- if (controller.getSelectedEventIDs().size() == 1) {
- try {
- EventNode eventNode = EventNode.createEventNode(Iterables.getOnlyElement(controller.getSelectedEventIDs()), controller.getEventsModel());
- SwingUtilities.invokeLater(() -> {
-
- Node[] eventNodes = new Node[]{eventNode};
- Children.Array children = new Children.Array();
- children.add(eventNodes);
-
- em.setRootContext(new AbstractNode(children));
- try {
- em.setSelectedNodes(eventNodes);
- System.out.println(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
-
- } catch (PropertyVetoException ex) {
- LOGGER.log(Level.SEVERE, "Explorer manager selection was vetoed.", ex); //NON-NLS
- }
- contentViewerPanel.setNode(eventNode);
- });
-
- } 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(jFXVizPanel.getScene().getWindow())
- .text(Bundle.TimelineTopComponent_selectedEventListener_errorMsg())
- .showError();
- });
- }
+ if (controller.getViewMode() == ViewMode.LIST) {
+ updateContentViewer();
} else {
- SwingUtilities.invokeLater(() -> contentViewerPanel.setNode(null));
+ updateResultView();
}
}
};
@@ -142,11 +120,17 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
contentViewerPanel = DataContentPanel.createInstance();
- this.contentViewerContainerPanel.add(contentViewerPanel, BorderLayout.CENTER);
- tlResultView = new TimeLineResultView(controller, contentViewerPanel);
- final DataResultPanel dataResultPanel = tlResultView.getDataResultPanel();
- this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
+ dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, contentViewerPanel);
+
+ lowerSplitXPane.setLeftComponent(dataResultPanel);
dataResultPanel.open();
+ lowerSplitXPane.setRightComponent(contentViewerPanel);
+
+ //set up listeners on relevant properties
+ TimeLineController.getTimeZone().addListener(timeZone -> dataResultPanel.setPath(getResultViewerSummaryString()));
+
+ controller.getSelectedEventIDs().addListener(selectedEventsListener);
+ updateResultView();
customizeFXComponents();
//Listen to ViewMode and adjust GUI componenets as needed.
@@ -156,28 +140,18 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
case DETAIL:
/*
* For counts and details mode, restore the result table at
- * the bottom left, if neccesary.
+ * the bottom left.
*/
- SwingUtilities.invokeLater(() -> {
- splitYPane.remove(contentViewerContainerPanel);
- if ((lowerSplitXPane.getParent() == splitYPane) == false) {
- splitYPane.add(lowerSplitXPane);
- lowerSplitXPane.add(contentViewerContainerPanel);
- }
- });
- controller.getSelectedEventIDs().removeListener(selectedEventListener);
+ controller.getSelectedEventIDs().removeListener(selectedEventsListener);
+ SwingUtilities.invokeLater(this::showResultTable);
break;
case LIST:
/*
* For list mode, remove the result table, and let the
* content viewer expand across the bottom.
*/
- SwingUtilities.invokeLater(() -> {
- splitYPane.remove(lowerSplitXPane);
- splitYPane.add(contentViewerContainerPanel);
- dataResultPanel.setNode(null);
- });
- controller.getSelectedEventIDs().addListener(selectedEventListener);
+ controller.getSelectedEventIDs().addListener(selectedEventsListener);
+ SwingUtilities.invokeLater(this::hideResultTable);
break;
default:
throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode());
@@ -256,12 +230,12 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
// //GEN-BEGIN:initComponents
private void initComponents() {
- jFXstatusPanel = new JFXPanel();
+ jFXstatusPanel = new javafx.embed.swing.JFXPanel();
splitYPane = new javax.swing.JSplitPane();
- jFXVizPanel = new JFXPanel();
+ jFXVizPanel = new javafx.embed.swing.JFXPanel();
lowerSplitXPane = new javax.swing.JSplitPane();
- resultContainerPanel = new javax.swing.JPanel();
- contentViewerContainerPanel = new javax.swing.JPanel();
+ leftFillerPanel = new javax.swing.JPanel();
+ rightfillerPanel = new javax.swing.JPanel();
jFXstatusPanel.setPreferredSize(new java.awt.Dimension(100, 16));
@@ -276,13 +250,31 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
lowerSplitXPane.setPreferredSize(new java.awt.Dimension(1200, 300));
lowerSplitXPane.setRequestFocusEnabled(false);
- resultContainerPanel.setPreferredSize(new java.awt.Dimension(700, 300));
- resultContainerPanel.setLayout(new java.awt.BorderLayout());
- lowerSplitXPane.setLeftComponent(resultContainerPanel);
+ javax.swing.GroupLayout leftFillerPanelLayout = new javax.swing.GroupLayout(leftFillerPanel);
+ leftFillerPanel.setLayout(leftFillerPanelLayout);
+ leftFillerPanelLayout.setHorizontalGroup(
+ leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 599, Short.MAX_VALUE)
+ );
+ leftFillerPanelLayout.setVerticalGroup(
+ leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 54, Short.MAX_VALUE)
+ );
- contentViewerContainerPanel.setPreferredSize(new java.awt.Dimension(500, 300));
- contentViewerContainerPanel.setLayout(new java.awt.BorderLayout());
- lowerSplitXPane.setRightComponent(contentViewerContainerPanel);
+ lowerSplitXPane.setLeftComponent(leftFillerPanel);
+
+ 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)
+ );
+
+ lowerSplitXPane.setRightComponent(rightfillerPanel);
splitYPane.setRightComponent(lowerSplitXPane);
@@ -291,10 +283,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(splitYPane, javax.swing.GroupLayout.DEFAULT_SIZE, 972, Short.MAX_VALUE)
- .addGroup(layout.createSequentialGroup()
- .addGap(0, 0, 0)
- .addComponent(jFXstatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addGap(0, 0, 0))
+ .addComponent(jFXstatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -306,11 +295,11 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
}// //GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
- private javax.swing.JPanel contentViewerContainerPanel;
private javafx.embed.swing.JFXPanel jFXVizPanel;
private javafx.embed.swing.JFXPanel jFXstatusPanel;
+ private javax.swing.JPanel leftFillerPanel;
private javax.swing.JSplitPane lowerSplitXPane;
- private javax.swing.JPanel resultContainerPanel;
+ private javax.swing.JPanel rightfillerPanel;
private javax.swing.JSplitPane splitYPane;
// End of variables declaration//GEN-END:variables
@@ -341,4 +330,98 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
public ExplorerManager getExplorerManager() {
return em;
}
+
+ /**
+ * @return a String representation of all the Events displayed
+ */
+ @NbBundle.Messages({
+ "TimeLineResultView.startDateToEndDate.text={0} to {1}"})
+ private String getResultViewerSummaryString() {
+ if (controller.getSelectedTimeRange().get() != null) {
+ final DateTimeFormatter zonedFormatter = TimeLineController.getZonedFormatter();
+ String start = controller.getSelectedTimeRange().get().getStart()
+ .withZone(TimeLineController.getJodaTimeZone())
+ .toString(zonedFormatter);
+ String end = controller.getSelectedTimeRange().get().getEnd()
+ .withZone(TimeLineController.getJodaTimeZone())
+ .toString(zonedFormatter);
+ return Bundle.TimeLineResultView_startDateToEndDate_text(start, end);
+ }
+ return "";
+ }
+
+ /**
+ * refresh this view with the events selected in the controller
+ */
+ private void updateResultView() {
+ Set 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,
+ controller.getEventsModel());
+
+ //this must be in edt or exception is thrown
+ SwingUtilities.invokeLater(() -> {
+ dataResultPanel.setPath(getResultViewerSummaryString());
+ dataResultPanel.setNode(root);
+ });
+ }
+ }
+
+ private void updateContentViewer() {
+ if (controller.getSelectedEventIDs().size() == 1) {
+ try {
+ EventNode eventNode = EventNode.createEventNode(Iterables.getOnlyElement(controller.getSelectedEventIDs()), controller.getEventsModel());
+ SwingUtilities.invokeLater(() -> {
+
+ Node[] eventNodes = new Node[]{eventNode};
+ Children.Array children = new Children.Array();
+ children.add(eventNodes);
+
+ em.setRootContext(new AbstractNode(children));
+ try {
+ em.setSelectedNodes(eventNodes);
+ System.out.println(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
+
+ } catch (PropertyVetoException ex) {
+ LOGGER.log(Level.SEVERE, "Explorer manager selection was vetoed.", ex); //NON-NLS
+ }
+ contentViewerPanel.setNode(eventNode);
+ });
+
+ } 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(jFXVizPanel.getScene().getWindow())
+ .text(Bundle.TimelineTopComponent_selectedEventListener_errorMsg())
+ .showError();
+ });
+ }
+ } else {
+ SwingUtilities.invokeLater(() -> contentViewerPanel.setNode(null));
+ }
+ }
+
+ private void showResultTable() {
+ splitYPane.remove(contentViewerPanel);
+
+ controller.getSelectedEventIDs().addListener(selectedEventsListener);
+ if ((lowerSplitXPane.getParent() == splitYPane) == false) {
+ splitYPane.setBottomComponent(lowerSplitXPane);
+ lowerSplitXPane.setRightComponent(contentViewerPanel);
+ }
+ lowerSplitXPane.setOneTouchExpandable(true);
+ lowerSplitXPane.setContinuousLayout(true);
+ lowerSplitXPane.resetToPreferredSizes();
+ }
+
+ private void hideResultTable() {
+ controller.getSelectedEventIDs().removeListener(selectedEventsListener);
+ splitYPane.setBottomComponent(contentViewerPanel);
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties
index b08bf41e42..498dfbaace 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties
@@ -35,7 +35,6 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years
Timeline.ui.ZoomRanges.fiveyears.text=Five Years
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
Timeline.ui.ZoomRanges.all.text=All
-TimeLineResultView.startDateToEndDate.text={0} to {1}
VisualizationPanel.histogramTask.title=Rebuild Histogram
VisualizationPanel.histogramTask.preparing=preparing
VisualizationPanel.histogramTask.resetUI=resetting ui
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties
index 2ba0472b32..3e0fab3823 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties
@@ -1,7 +1,7 @@
Timeline.node.root=\u30eb\u30fc\u30c8
Timeline.ui.TimeLineChart.tooltip.text=\u30c0\u30d6\u30eb\u30af\u30ea\u30c3\u30af\u3067\u4e0b\u8a18\u306e\u7bc4\u56f2\u3078\u30ba\u30fc\u30e0\uff1a\n{0}\u301c{1}\n\u53f3\u30af\u30ea\u30c3\u30af\u3067\u5143\u306b\u623b\u308a\u307e\u3059\u3002
Timeline.ui.ZoomRanges.all.text=\u5168\u3066
-TimeLineResultView.startDateToEndDate.text={0}\u304b\u3089{1}
+
VisualizationPanel.histogramTask.preparing=\u6e96\u5099\u4e2d
VisualizationPanel.histogramTask.queryDb=DB\u3092\u30af\u30a8\u30ea\u4e2d
VisualizationPanel.histogramTask.resetUI=ui\u3092\u518d\u8a2d\u5b9a\u4e2d
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java
deleted file mode 100644
index 9fb308229f..0000000000
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2013 Basis Technology Corp.
- * Contact: carrier sleuthkit 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 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(timeZone -> dataResultPanel.setPath(getSummaryString()));
-
- controller.getSelectedEventIDs().addListener((Observable selectedIDs) -> 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 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);
- });
- }
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java
index 1f96d9cd49..dd96f3c7a3 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
@@ -289,10 +290,9 @@ class ListTimeline extends BorderPane {
if (element == null) {
menuItems.add(new SeparatorMenuItem());
} else {
- String actionName = element.getValue(Action.NAME).toString();
+ String actionName = Objects.toString(element.getValue(Action.NAME));
if (Arrays.asList("&Properties", "Tools").contains(actionName) == false) {
-
if (element instanceof Presenter.Popup) {
JMenuItem submenu = ((Presenter.Popup) element).getPopupPresenter();
menuItems.add(SwingMenuItemAdapter.create(submenu));