diff --git a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties index bf26cc976e..9f2e0c950c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties @@ -24,10 +24,7 @@ TimelinePanel.jButton7.text=3d TimelinePanel.jButton2.text=1m TimelinePanel.jButton3.text=3m TimelinePanel.jButton4.text=2w -TimeLineTopComponent.eventsTab.name=Events -TimeLineTopComponent.filterTab.name=Filters OpenTimelineAction.title=Timeline OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources. -TimeLineTopComponent.timeZonePanel.text=Display Times In\: ProgressWindow.progressHeader.text=\ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index fa84990bf4..4587e26122 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -316,8 +316,10 @@ public class TimeLineController { LOGGER.log(Level.INFO, "Beginning generation of timeline"); // NON-NLS try { SwingUtilities.invokeLater(() -> { - if (isWindowOpen()) { - mainFrame.close(); + synchronized (TimeLineController.this) { + if (isWindowOpen()) { + mainFrame.close(); + } } }); final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); @@ -362,8 +364,10 @@ public class TimeLineController { void rebuildTagsTable() { LOGGER.log(Level.INFO, "starting to rebuild tags table"); // NON-NLS SwingUtilities.invokeLater(() -> { - if (isWindowOpen()) { - mainFrame.close(); + synchronized (TimeLineController.this) { + if (isWindowOpen()) { + mainFrame.close(); + } } }); synchronized (eventsRepository) { @@ -388,16 +392,19 @@ public class TimeLineController { IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener); IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener); Case.removePropertyChangeListener(caseListener); - mainFrame.close(); - mainFrame.setVisible(false); - mainFrame = null; + SwingUtilities.invokeLater(() -> { + synchronized (TimeLineController.this) { + mainFrame.close(); + mainFrame = null; + } + }); } } /** * show the timeline window and prompt for rebuilding database if necessary. */ - synchronized void openTimeLine() { + void openTimeLine() { // listen for case changes (specifically images being added, and case changes). if (Case.isCaseOpen() && !listeningToAutopsy) { IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener); @@ -539,20 +546,20 @@ public class TimeLineController { /** * private method to build gui if necessary and make it visible. */ - synchronized private void showWindow() { - if (mainFrame == null) { - LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS - mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent( - NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction")); - if (mainFrame == null) { - mainFrame = new TimeLineTopComponent(); - } - mainFrame.setController(this); - } + private void showWindow() { SwingUtilities.invokeLater(() -> { - mainFrame.open(); - mainFrame.setVisible(true); - mainFrame.toFront(); + synchronized (TimeLineController.this) { + if (mainFrame == null) { + LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS + mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent( + NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction")); + if (mainFrame == null) { + mainFrame = new TimeLineTopComponent(this); + } + } + mainFrame.open(); + mainFrame.toFront(); + } }); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index f19dc9bf1c..97821ce8e2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -23,7 +23,7 @@ import java.util.Collections; import java.util.List; import javafx.application.Platform; import javafx.beans.Observable; -import javafx.event.ActionEvent; +import javafx.embed.swing.JFXPanel; import javafx.scene.Scene; import javafx.scene.control.SplitPane; import javafx.scene.control.Tab; @@ -34,7 +34,6 @@ import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import org.netbeans.api.settings.ConvertAsProperties; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; import org.openide.util.NbBundle; @@ -51,135 +50,103 @@ 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.NavPanel; +import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree; import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel; import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane; /** * TopComponent for the timeline feature. */ -@ConvertAsProperties( - dtd = "-//org.sleuthkit.autopsy.timeline//TimeLine//EN", - autostore = false) @TopComponent.Description( preferredID = "TimeLineTopComponent", //iconBase="SET/PATH/TO/ICON/HERE", persistenceType = TopComponent.PERSISTENCE_NEVER) @TopComponent.Registration(mode = "timeline", openAtStartup = false) -public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider, TimeLineUI { +public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider { private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName()); - private DataContentPanel dataContentPanel; + private final DataContentPanel dataContentPanel; - private TimeLineResultView tlrv; + private final TimeLineResultView tlrv; private final ExplorerManager em = new ExplorerManager(); - private TimeLineController controller; + private final TimeLineController controller; - ////jfx componenets that make up the interface - private final FilterSetPanel filtersPanel = new FilterSetPanel(); - - private final Tab eventsTab = new Tab( - NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.eventsTab.name")); - - private final Tab filterTab = new Tab( - NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.filterTab.name")); - - private final VBox leftVBox = new VBox(5); - - private final NavPanel navPanel = new NavPanel(); - - private final StatusBar statusBar = new StatusBar(); - - private final TabPane tabPane = new TabPane(); - - private final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(); - - private final VisualizationPanel visualizationPanel = new VisualizationPanel(navPanel); - - private final SplitPane splitPane = new SplitPane(); - - private final TimeZonePanel timeZonePanel = new TimeZonePanel(); - - public TimeLineTopComponent() { + public TimeLineTopComponent(TimeLineController controller) { initComponents(); - + this.controller = controller; associateLookup(ExplorerUtils.createLookup(em, getActionMap())); setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent")); setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent")); setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application - timeZonePanel.setText(NbBundle.getMessage(this.getClass(), "TimeLineTopComponent.timeZonePanel.text")); - customizeComponents(); - } - - synchronized private void customizeComponents() { - dataContentPanel = DataContentPanel.createInstance(); this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER); - tlrv = new TimeLineResultView(dataContentPanel); + tlrv = new TimeLineResultView(controller, dataContentPanel); DataResultPanel dataResultPanel = tlrv.getDataResultPanel(); this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER); dataResultPanel.open(); - - Platform.runLater(() -> { - //assemble ui componenets together - jFXstatusPanel.setScene(new Scene(statusBar)); - jFXVizPanel.setScene(new Scene(splitPane)); - - splitPane.setDividerPositions(0); - - filterTab.setClosable(false); - filterTab.setContent(filtersPanel); - filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS - - eventsTab.setClosable(false); - eventsTab.setContent(navPanel); - eventsTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS - - tabPane.getTabs().addAll(filterTab, eventsTab); - VBox.setVgrow(tabPane, Priority.ALWAYS); - - VBox.setVgrow(timeZonePanel, Priority.SOMETIMES); - leftVBox.getChildren().addAll(timeZonePanel, zoomSettingsPane, tabPane); - - SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE); - splitPane.getItems().addAll(leftVBox, visualizationPanel); - }); + customizeFXComponents(); } - @Override - public synchronized void setController(TimeLineController controller) { - this.controller = controller; - - tlrv.setController(controller); + @NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events", + "TimeLineTopComponent.filterTab.name=Filters"}) + void customizeFXComponents() { Platform.runLater(() -> { - jFXVizPanel.getScene().addEventFilter(KeyEvent.KEY_PRESSED, - (KeyEvent event) -> { - if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) { - new Back(controller).handle(new ActionEvent()); - } else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) { - new Back(controller).handle(new ActionEvent()); - } else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) { - new Forward(controller).handle(new ActionEvent()); - } else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) { - new Forward(controller).handle(new ActionEvent()); - } - }); + + //create and wire up jfx componenets that make up the interface + final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller)); + filterTab.setClosable(false); + filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS + + final EventsTree eventsTree = new EventsTree(controller); + final VisualizationPanel visualizationPanel = new VisualizationPanel(controller, eventsTree); + final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree); + eventsTreeTab.setClosable(false); + eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS + eventsTreeTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS)); + + final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab); + VBox.setVgrow(leftTabPane, Priority.ALWAYS); controller.viewModeProperty().addListener((Observable observable) -> { if (controller.viewModeProperty().get().equals(VisualizationMode.COUNTS)) { - tabPane.getSelectionModel().select(filterTab); + //if view mode is counts, make sure events tabd is not active + leftTabPane.getSelectionModel().select(filterTab); } }); - eventsTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS)); - visualizationPanel.setController(controller); - navPanel.setController(controller); - filtersPanel.setController(controller); - zoomSettingsPane.setController(controller); - statusBar.setController(controller); + + final TimeZonePanel timeZonePanel = new TimeZonePanel(); + VBox.setVgrow(timeZonePanel, Priority.SOMETIMES); + + final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller); + + final VBox leftVBox = new VBox(5, timeZonePanel, zoomSettingsPane, leftTabPane); + SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE); + + final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel); + mainSplitPane.setDividerPositions(0); + + final Scene scene = new Scene(mainSplitPane); + scene.addEventFilter(KeyEvent.KEY_PRESSED, + (KeyEvent event) -> { + if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) { + new Back(controller).handle(null); + } else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) { + new Back(controller).handle(null); + } else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) { + new Forward(controller).handle(null); + } else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) { + new Forward(controller).handle(null); + } + }); + + //add ui componenets to JFXPanels + jFXVizPanel.setScene(scene); + jFXstatusPanel.setScene(new Scene(new StatusBar(controller))); + }); } @@ -196,9 +163,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer // //GEN-BEGIN:initComponents private void initComponents() { - jFXstatusPanel = new javafx.embed.swing.JFXPanel(); + jFXstatusPanel = new JFXPanel(); splitYPane = new javax.swing.JSplitPane(); - jFXVizPanel = new javafx.embed.swing.JFXPanel(); + jFXVizPanel = new JFXPanel(); lowerSplitXPane = new javax.swing.JSplitPane(); resultContainerPanel = new javax.swing.JPanel(); contentViewerContainerPanel = new javax.swing.JPanel(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineUI.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineUI.java deleted file mode 100644 index bb14f76aee..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineUI.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2014 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; - -/** - * - */ -public interface TimeLineUI { - - void setController(TimeLineController controller); -} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineView.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineView.java deleted file mode 100644 index 4c880c9cf0..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineView.java +++ /dev/null @@ -1,36 +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; - -/** - * Interface to be implemented by views of the data. - * - * Most implementations should install the relevant listeners in their - * {@link #setController} and {@link #setModel} methods - */ -import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; - -public interface TimeLineView extends TimeLineUI { - - @Override - void setController(TimeLineController controller); - - void setModel(final FilteredEventsModel filteredEvents); - -} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/actions/Bundle.properties index dd21fc1097..1140e0d19b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/Bundle.properties @@ -1,4 +1,4 @@ Back.actions.name.text=Back DefaultFilters.action.name.text=apply default filters Forward.action.name.text=Forward -ZoomOut.action.name.text=apply default filters \ No newline at end of file + diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java index 135d3cc45a..8faa1bfe53 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java @@ -53,7 +53,8 @@ public class SaveSnapshotAsReport extends Action { private static final Logger LOGGER = Logger.getLogger(SaveSnapshotAsReport.class.getName()); - @NbBundle.Messages({"SaveSnapshot.action.name.text=save snapshot", "SaveSnapshot.fileChoose.title.text=Save snapshot to"}) + @NbBundle.Messages({"SaveSnapshot.action.name.text=save snapshot", + "SaveSnapshot.fileChoose.title.text=Save snapshot to"}) public SaveSnapshotAsReport(TimeLineController controller, WritableImage snapshot) { super(Bundle.SaveSnapshot_action_name_text()); setEventHandler(new Consumer() { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomIn.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomIn.java new file mode 100644 index 0000000000..d1f76c3c7e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomIn.java @@ -0,0 +1,44 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.actions; + +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import org.controlsfx.control.action.Action; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.timeline.TimeLineController; + +/** + * + */ +public class ZoomIn extends Action { + + private static final Image MAGNIFIER_IN = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-in-green.png"); //NOI18N + + @NbBundle.Messages({"ZoomIn.longText=Zoom in to view half as much time.", + "ZoomIn.action.text=Zoom in"}) + public ZoomIn(TimeLineController controller) { + super(Bundle.ZoomIn_action_text()); + setLongText(Bundle.ZoomIn_longText()); + setGraphic(new ImageView(MAGNIFIER_IN)); + setEventHandler(actionEvent -> { + controller.pushZoomInTime(); + }); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomOut.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomOut.java index ee362dfc40..8818ff4557 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomOut.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomOut.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2015 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,8 @@ package org.sleuthkit.autopsy.timeline.actions; import javafx.beans.binding.BooleanBinding; -import javafx.event.ActionEvent; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import org.controlsfx.control.action.Action; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.TimeLineController; @@ -30,15 +31,22 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; */ public class ZoomOut extends Action { - private final TimeLineController controller; + private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png"); //NOI18N - private final FilteredEventsModel eventsModel; + @NbBundle.Messages({"ZoomOut.longText=Zoom out to view 50% more time.", + "ZoomOut.action.text=Zoom out"}) + public ZoomOut(TimeLineController controller) { + super(Bundle.ZoomOut_action_text()); + setLongText(Bundle.ZoomOut_longText()); + setGraphic(new ImageView(MAGNIFIER_OUT)); + setEventHandler(actionEvent -> { + controller.pushZoomOutTime(); + }); - public ZoomOut(final TimeLineController controller) { - super(NbBundle.getMessage(ZoomOut.class, "ZoomOut.action.name.text")); - this.controller = controller; - eventsModel = controller.getEventsModel(); + //disable action when the current time range already encompases the entire case. disabledProperty().bind(new BooleanBinding() { + private final FilteredEventsModel eventsModel = controller.getEventsModel(); + { bind(eventsModel.zoomParametersProperty()); } @@ -48,8 +56,5 @@ public class ZoomOut extends Action { return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval()); } }); - setEventHandler((ActionEvent t) -> { - controller.zoomOutToActivity(); - }); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomToEvents.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomToEvents.java new file mode 100644 index 0000000000..7bcc076936 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomToEvents.java @@ -0,0 +1,61 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-15 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.actions; + +import javafx.beans.binding.BooleanBinding; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import org.controlsfx.control.action.Action; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; + +/** + * + */ +public class ZoomToEvents extends Action { + + private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png", 16, 16, true, true); //NOI18N + + @NbBundle.Messages({"ZoomToEvents.action.text=Zoom to events", + "ZoomToEvents.longText=Zoom out to show the nearest events."}) + public ZoomToEvents(final TimeLineController controller) { + super(Bundle.ZoomToEvents_action_text()); + setLongText(Bundle.ZoomToEvents_longText()); + setGraphic(new ImageView(MAGNIFIER_OUT)); + setEventHandler(actionEvent -> { + controller.zoomOutToActivity(); + }); + + //disable action when the current time range already encompases the entire case. + disabledProperty().bind(new BooleanBinding() { + private final FilteredEventsModel eventsModel = controller.getEventsModel(); + + { + bind(eventsModel.zoomParametersProperty()); + } + + @Override + protected boolean computeValue() { + //TODO: do a db query to see if using this action will actually result in viewable events + return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval()); + } + }); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index ae354f87f8..f34c46fc64 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -41,7 +41,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.timeline.TimeLineView; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType; import org.sleuthkit.autopsy.timeline.db.EventsRepository; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java index 6641c7515b..8d16c7a86f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java @@ -51,13 +51,11 @@ import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; -import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.TimeLineView; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; @@ -75,7 +73,7 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; * {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up * common history context menu items out of derived classes? -jm */ -public abstract class AbstractVisualizationPane & TimeLineChart> extends BorderPane implements TimeLineView { +public abstract class AbstractVisualizationPane & TimeLineChart> extends BorderPane { @NbBundle.Messages("AbstractVisualization.Drag_Tooltip.text=Drag the mouse to select a time interval to zoom into.") private static final Tooltip DRAG_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Drag_Tooltip_text()); @@ -102,11 +100,15 @@ public abstract class AbstractVisualizationPane */ private Task updateTask; - protected TimeLineController controller; + final protected TimeLineController controller; - protected FilteredEventsModel filteredEvents; + final protected FilteredEventsModel filteredEvents; - protected ReadOnlyListWrapper selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList()); + final protected ReadOnlyListWrapper selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList()); + + private InvalidationListener invalidationListener = (Observable observable) -> { + update(); + }; public ReadOnlyListProperty getSelectedNodes() { return selectedNodes.getReadOnlyProperty(); @@ -186,7 +188,7 @@ public abstract class AbstractVisualizationPane * Primarily this invokes the background {@link Task} returned by * {@link #getUpdateTask()} which derived classes must implement. */ - synchronized public void update() { + final synchronized public void update() { if (updateTask != null) { updateTask.cancel(true); updateTask = null; @@ -212,7 +214,7 @@ public abstract class AbstractVisualizationPane controller.monitorTask(updateTask); } - synchronized public void dispose() { + final synchronized public void dispose() { if (updateTask != null) { updateTask.cancel(true); } @@ -220,7 +222,12 @@ public abstract class AbstractVisualizationPane invalidationListener = null; } - protected AbstractVisualizationPane(Pane partPane, Pane contextPane, Region spacer) { + protected AbstractVisualizationPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) { + this.controller = controller; + + this.filteredEvents = controller.getEventsModel(); + this.filteredEvents.registerForEvents(this); + this.filteredEvents.zoomParametersProperty().addListener(invalidationListener); this.leafPane = partPane; this.branchPane = contextPane; this.spacer = spacer; @@ -235,18 +242,9 @@ public abstract class AbstractVisualizationPane }); } }); - } - - @Override - synchronized public void setController(TimeLineController controller) { - this.controller = controller; - chart.setController(controller); - - setModel(controller.getEventsModel()); - TimeLineController.getTimeZone().addListener((Observable observable) -> { - update(); - }); + TimeLineController.getTimeZone().addListener(invalidationListener); + //show tooltip text in status bar hoverProperty().addListener((observable, oldActivated, newActivated) -> { if (newActivated) { @@ -255,21 +253,7 @@ public abstract class AbstractVisualizationPane controller.setStatus(""); } }); - } - - @Override - synchronized public void setModel(@Nonnull FilteredEventsModel filteredEvents) { - - if (this.filteredEvents != null && this.filteredEvents != filteredEvents) { - this.filteredEvents.unRegisterForEvents(this); - this.filteredEvents.zoomParametersProperty().removeListener(invalidationListener); - } - if (this.filteredEvents != filteredEvents) { - filteredEvents.registerForEvents(this); - filteredEvents.zoomParametersProperty().addListener(invalidationListener); - } - this.filteredEvents = filteredEvents; - + update(); } @@ -278,10 +262,6 @@ public abstract class AbstractVisualizationPane update(); } - protected InvalidationListener invalidationListener = (Observable observable) -> { - update(); - }; - /** * iterate through the list of tick-marks building a two level structure of * replacement tick marl labels. (Visually) upper level has most diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java index 8ff17e4920..ce8b2b3e5c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/StatusBar.java @@ -28,13 +28,12 @@ import javafx.scene.layout.Region; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.TimeLineUI; /** * simple status bar that only shows one possible message determined by * {@link TimeLineController#newEventsFlag} */ -public class StatusBar extends ToolBar implements TimeLineUI { +public class StatusBar extends ToolBar { private TimeLineController controller; @@ -56,7 +55,8 @@ public class StatusBar extends ToolBar implements TimeLineUI { @FXML private Label messageLabel; - public StatusBar() { + public StatusBar(TimeLineController controller) { + this.controller = controller; FXMLConstructor.construct(this, "StatusBar.fxml"); // NON-NLS } @@ -73,11 +73,7 @@ public class StatusBar extends ToolBar implements TimeLineUI { taskLabel.setText(NbBundle.getMessage(this.getClass(), "StatusBar.taskLabel.text")); taskLabel.setVisible(false); HBox.setHgrow(spacer, Priority.ALWAYS); - } - @Override - public void setController(TimeLineController controller) { - this.controller = controller; refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag()); refreshLabel.managedProperty().bind(this.controller.getNewEventsFlag()); taskLabel.textProperty().bind(this.controller.getTaskTitle()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java index 5fb91a3193..db366db707 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java @@ -29,7 +29,6 @@ import javafx.scene.input.MouseEvent; import org.controlsfx.control.action.ActionGroup; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.TimeLineView; import org.sleuthkit.autopsy.timeline.actions.Back; import org.sleuthkit.autopsy.timeline.actions.Forward; @@ -38,8 +37,9 @@ import org.sleuthkit.autopsy.timeline.actions.Forward; * * @param the type of values along the horizontal axis */ -public interface TimeLineChart extends TimeLineView { +public interface TimeLineChart { +// void setController(TimeLineController controller); IntervalSelector getIntervalSelector(); void setIntervalSelector(IntervalSelector newIntervalSelector); @@ -139,6 +139,17 @@ public interface TimeLineChart extends TimeLineView { } } + /** + * enum to represent whether the drag is a left/right-edge modification or a + * horizontal slide triggered by dragging the center + */ + enum DragPosition { + + LEFT, + CENTER, + RIGHT + } + @NbBundle.Messages({"TimeLineChart.zoomHistoryActionGroup.name=Zoom History"}) static ActionGroup newZoomHistoyActionGroup(TimeLineController controller) { return new ActionGroup(Bundle.TimeLineChart_zoomHistoryActionGroup_name(), diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java index 2ae27896d8..f61e2b5b28 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java @@ -25,12 +25,11 @@ import javax.swing.SwingUtilities; import org.joda.time.format.DateTimeFormatter; import org.openide.nodes.Node; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.timeline.TimeLineController; -import org.sleuthkit.autopsy.timeline.TimeLineView; -import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; -import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode; 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 @@ -39,16 +38,16 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel; * {@link DataResultPanel}. That is, this class acts as a sort of bridge/adapter * between a FilteredEventsModel instance and a DataResultPanel instance. */ -public class TimeLineResultView implements TimeLineView { +public class TimeLineResultView { /** * the {@link DataResultPanel} that is the real view proxied by this class */ private final DataResultPanel dataResultPanel; - private TimeLineController controller; + private final TimeLineController controller; - private FilteredEventsModel filteredEvents; + private final FilteredEventsModel filteredEvents; private Set selectedEventIDs = new HashSet<>(); @@ -56,19 +55,11 @@ public class TimeLineResultView implements TimeLineView { return dataResultPanel; } - public TimeLineResultView(DataContent dataContent) { - dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent); - } + public TimeLineResultView(TimeLineController controller, DataContent dataContent) { - /** - * Set the Controller for this class. Also sets the model provided by the - * controller as the model for this view. - * - * @param controller - */ - @Override - public void setController(TimeLineController controller) { 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) -> { @@ -78,18 +69,7 @@ public class TimeLineResultView implements TimeLineView { controller.getSelectedEventIDs().addListener((Observable o) -> { refresh(); }); - - setModel(controller.getEventsModel()); - } - - /** - * Set the Model for this View - * - * @param filteredEvents - */ - @Override - synchronized public void setModel(final FilteredEventsModel filteredEvents) { - this.filteredEvents = filteredEvents; + refresh(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeZonePanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeZonePanel.java index 67ee1a65a7..8304c6ab43 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeZonePanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeZonePanel.java @@ -23,7 +23,10 @@ import java.util.Date; import java.util.TimeZone; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; -import javafx.scene.control.*; +import javafx.scene.control.RadioButton; +import javafx.scene.control.TitledPane; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.FXMLConstructor; import org.sleuthkit.autopsy.timeline.TimeLineController; @@ -50,8 +53,9 @@ public class TimeZonePanel extends TitledPane { } @FXML + @NbBundle.Messages({"TimeZonePanel.title=Display Times In:"}) public void initialize() { - + setText(Bundle.TimeZonePanel_title()); // localRadio.setText("Local Time Zone: " + getTimeZoneString(TimeZone.getDefault())); localRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.localRadio.text")); otherRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.otherRadio.text")); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml index 912d7e3e01..ac2748668b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml @@ -9,7 +9,7 @@ - + @@ -115,7 +115,7 @@ - -