1
0
mirror of https://github.com/elisspace/autopsy.git synced 2026-09-05 18:14:30 +00:00

cleanup and refactor TimeLineTopComponent

This commit is contained in:
jmillman
2016-05-20 15:20:25 -04:00
parent ffef4926a1
commit 7a0bfb4980
4 changed files with 170 additions and 222 deletions

View File

@@ -188,7 +188,7 @@ public class TimeLineController {
}
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private TimeLineTopComponent mainFrame;
private TimeLineTopComponent topComponent;
//are the listeners currently attached
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@@ -227,19 +227,32 @@ public class TimeLineController {
private final PromptDialogManager promptDialogManager = new PromptDialogManager(this);
/**
* @return A list of the selected event ids
* Get an ObservableList of selected event IDs
*
* @return A list of the selected event IDs
*/
synchronized public ObservableList<Long> getSelectedEventIDs() {
return selectedEventIDs;
}
/**
* @return a read only view of the selected interval.
* 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> getSelectedTimeRange() {
synchronized public ReadOnlyObjectProperty<Interval> selectedTimeRangeProperty() {
return selectedTimeRange.getReadOnlyProperty();
}
/**
* Get the selected time range.
*
* @return The selected time range.
*/
synchronized public Interval getSelectedTimeRange() {
return selectedTimeRange.get();
}
public ReadOnlyBooleanProperty eventsDBStaleProperty() {
return eventsDBStale.getReadOnlyProperty();
}
@@ -470,9 +483,9 @@ public class TimeLineController {
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
Case.removePropertyChangeListener(caseListener);
if (mainFrame != null) {
mainFrame.close();
mainFrame = null;
if (topComponent != null) {
topComponent.close();
topComponent = null;
}
OpenTimelineAction.invalidateController();
}
@@ -634,16 +647,16 @@ public class TimeLineController {
*/
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
synchronized private void showWindow() {
if (mainFrame == null) {
mainFrame = new TimeLineTopComponent(this);
if (topComponent == null) {
topComponent = new TimeLineTopComponent(this);
}
mainFrame.open();
mainFrame.toFront();
topComponent.open();
topComponent.toFront();
/*
* Make this top component active so its ExplorerManager's lookup gets
* proxied in Utilities.actionsGlobalContext()
*/
mainFrame.requestActive();
topComponent.requestActive();
}
synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) {

View File

@@ -65,7 +65,7 @@
<Property name="useNullLayout" type="boolean" value="true"/>
</Layout>
</Container>
<Container class="javax.swing.JSplitPane" name="lowerSplitXPane">
<Container class="javax.swing.JSplitPane" name="horizontalSplitPane">
<Properties>
<Property name="dividerLocation" type="int" value="600"/>
<Property name="resizeWeight" type="double" value="0.5"/>

View File

@@ -18,14 +18,13 @@
*/
package org.sleuthkit.autopsy.timeline;
import com.google.common.collect.Iterables;
import java.beans.PropertyVetoException;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
@@ -38,14 +37,12 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javax.swing.SwingUtilities;
import org.controlsfx.control.Notifications;
import org.joda.time.Interval;
import org.joda.time.format.DateTimeFormatter;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.windows.Mode;
import org.openide.windows.TopComponent;
import static org.openide.windows.TopComponent.PROP_UNDOCKING_DISABLED;
@@ -65,7 +62,6 @@ import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel;
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
@@ -91,43 +87,85 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
private final TimeLineController controller;
@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.
* 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) {
if (controller.getViewMode() == ViewMode.LIST) {
updateContentViewer();
} else {
updateResultView();
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:
if (selectedEventIDs.size() == 1) {
//if there is only one event selected, make a explorer node for it and push it to the content viewer.
try {
EventNode eventNode = EventNode.createEventNode(selectedEventIDs.get(0), controller.getEventsModel());
SwingUtilities.invokeLater(() -> 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 {
//There is more than one or no event selected, so clear the content viewer.
SwingUtilities.invokeLater(() -> contentViewerPanel.setNode(null));
}
break;
case COUNTS:
case DETAIL:
//make a root node with nodes for the selected events as children and push it to the result viewer.
EventRootNode rootNode = new EventRootNode(selectedEventIDs, controller.getEventsModel());
SwingUtilities.invokeLater(() -> {
dataResultPanel.setPath(getResultViewerSummaryString());
dataResultPanel.setNode(rootNode);
});
break;
default:
throw new UnsupportedOperationException("Unknown view mode: " + controller.getViewMode());
}
}
};
/**
* Constructor
*
* @param controller The TimeLineController for this topcomponent.
*/
public TimeLineTopComponent(TimeLineController controller) {
initComponents();
this.controller = controller;
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
this.controller = controller;
//create linked result and content views
contentViewerPanel = DataContentPanel.createInstance();
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, contentViewerPanel);
lowerSplitXPane.setLeftComponent(dataResultPanel);
dataResultPanel.open();
lowerSplitXPane.setRightComponent(contentViewerPanel);
//add them to bottom splitpane
horizontalSplitPane.setLeftComponent(dataResultPanel);
horizontalSplitPane.setRightComponent(contentViewerPanel);
//set up listeners on relevant properties
dataResultPanel.open(); //get the explorermanager
Platform.runLater(this::initFXComponents);
//set up listeners
TimeLineController.getTimeZone().addListener(timeZone -> dataResultPanel.setPath(getResultViewerSummaryString()));
controller.getSelectedEventIDs().addListener(selectedEventsListener);
updateResultView();
customizeFXComponents();
//Listen to ViewMode and adjust GUI componenets as needed.
controller.viewModeProperty().addListener(viewMode -> {
@@ -138,16 +176,22 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
* For counts and details mode, restore the result table at
* the bottom left.
*/
controller.getSelectedEventIDs().removeListener(selectedEventsListener);
SwingUtilities.invokeLater(this::showResultTable);
SwingUtilities.invokeLater(() -> {
splitYPane.remove(contentViewerPanel);
if ((horizontalSplitPane.getParent() == splitYPane) == false) {
splitYPane.setBottomComponent(horizontalSplitPane);
horizontalSplitPane.setRightComponent(contentViewerPanel);
}
});
break;
case LIST:
/*
* For list mode, remove the result table, and let the
* content viewer expand across the bottom.
*/
controller.getSelectedEventIDs().addListener(selectedEventsListener);
SwingUtilities.invokeLater(this::hideResultTable);
SwingUtilities.invokeLater(() -> {
splitYPane.setBottomComponent(contentViewerPanel);
});
break;
default:
throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode());
@@ -155,62 +199,65 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
});
}
@NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events",
/**
* Create and wire up JavaFX components of the interface
*/
@NbBundle.Messages({
"TimeLineTopComponent.eventsTab.name=Events",
"TimeLineTopComponent.filterTab.name=Filters"})
void customizeFXComponents() {
Platform.runLater(() -> {
@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);
//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
//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
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().isNotEqualTo(ViewMode.DETAIL));
//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 counts, make sure events tab is not active
leftTabPane.getSelectionModel().select(filterTab);
}
});
HistoryToolBar historyToolBar = new HistoryToolBar(controller);
final TimeZonePanel timeZonePanel = new TimeZonePanel();
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
final VBox leftVBox = new VBox(5, timeZonePanel, historyToolBar, zoomSettingsPane, leftTabPane);
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel);
mainSplitPane.setDividerPositions(0);
final Scene scene = new Scene(mainSplitPane);
scene.addEventFilter(KeyEvent.KEY_PRESSED,
(KeyEvent event) -> {
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
new Back(controller).handle(null);
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
new Back(controller).handle(null);
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
new Forward(controller).handle(null);
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
new Forward(controller).handle(null);
}
});
//add ui componenets to JFXPanels
jFXVizPanel.setScene(scene);
jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
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 VisualizationPanel visualizationPanel = new VisualizationPanel(controller, eventsTree);
final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel);
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
jFXVizPanel.setScene(scene);
jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
}
@Override
@@ -229,7 +276,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
jFXstatusPanel = new javafx.embed.swing.JFXPanel();
splitYPane = new javax.swing.JSplitPane();
jFXVizPanel = new javafx.embed.swing.JFXPanel();
lowerSplitXPane = new javax.swing.JSplitPane();
horizontalSplitPane = new javax.swing.JSplitPane();
leftFillerPanel = new javax.swing.JPanel();
rightfillerPanel = new javax.swing.JPanel();
@@ -241,10 +288,10 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
splitYPane.setPreferredSize(new java.awt.Dimension(1024, 400));
splitYPane.setLeftComponent(jFXVizPanel);
lowerSplitXPane.setDividerLocation(600);
lowerSplitXPane.setResizeWeight(0.5);
lowerSplitXPane.setPreferredSize(new java.awt.Dimension(1200, 300));
lowerSplitXPane.setRequestFocusEnabled(false);
horizontalSplitPane.setDividerLocation(600);
horizontalSplitPane.setResizeWeight(0.5);
horizontalSplitPane.setPreferredSize(new java.awt.Dimension(1200, 300));
horizontalSplitPane.setRequestFocusEnabled(false);
javax.swing.GroupLayout leftFillerPanelLayout = new javax.swing.GroupLayout(leftFillerPanel);
leftFillerPanel.setLayout(leftFillerPanelLayout);
@@ -257,7 +304,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
.addGap(0, 54, Short.MAX_VALUE)
);
lowerSplitXPane.setLeftComponent(leftFillerPanel);
horizontalSplitPane.setLeftComponent(leftFillerPanel);
javax.swing.GroupLayout rightfillerPanelLayout = new javax.swing.GroupLayout(rightfillerPanel);
rightfillerPanel.setLayout(rightfillerPanelLayout);
@@ -270,9 +317,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
.addGap(0, 54, Short.MAX_VALUE)
);
lowerSplitXPane.setRightComponent(rightfillerPanel);
horizontalSplitPane.setRightComponent(rightfillerPanel);
splitYPane.setRightComponent(lowerSplitXPane);
splitYPane.setRightComponent(horizontalSplitPane);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
@@ -291,10 +338,10 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JSplitPane horizontalSplitPane;
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 rightfillerPanel;
private javax.swing.JSplitPane splitYPane;
// End of variables declaration//GEN-END:variables
@@ -305,115 +352,34 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
putClientProperty(PROP_UNDOCKING_DISABLED, true);
}
@Override
public void componentClosed() {
// TODO add custom code on component closing
}
void writeProperties(java.util.Properties p) {
// better to version settings since initial version as advocated at
// http://wiki.apidesign.org/wiki/PropertyFiles
p.setProperty("version", "1.0");
// TODO store your settings
}
void readProperties(java.util.Properties p) {
String version = p.getProperty("version");
// TODO read your settings according to their version
}
@Override
public ExplorerManager getExplorerManager() {
return em;
}
/**
* @return a String representation of all the Events displayed
* 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() {
if (controller.getSelectedTimeRange().get() != null) {
Interval selectedTimeRange = controller.getSelectedTimeRange();
if (selectedTimeRange == null) {
return "";
} else {
final DateTimeFormatter zonedFormatter = TimeLineController.getZonedFormatter();
String start = controller.getSelectedTimeRange().get().getStart()
String start = selectedTimeRange.getStart()
.withZone(TimeLineController.getJodaTimeZone())
.toString(zonedFormatter);
String end = controller.getSelectedTimeRange().get().getEnd()
String end = selectedTimeRange.getEnd()
.withZone(TimeLineController.getJodaTimeZone())
.toString(zonedFormatter);
return Bundle.TimeLineResultView_startDateToEndDate_text(start, end);
}
return "";
}
/**
* Update the DataResultPanel with the events selected in the controller.
*/
private void updateResultView() {
final EventRootNode root = new EventRootNode(
controller.getSelectedEventIDs(),
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);
}
}

View File

@@ -1,31 +0,0 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.sleuthkit.autopsy.timeline.explorernodes;
import javafx.scene.Node;
import javafx.scene.Parent;
import org.openide.explorer.ExplorerManager;
/**
*
*/
public class ExplorerUtils {
static public ExplorerManager find(Node node) {
if (node instanceof ExplorerManager.Provider) {
return ((ExplorerManager.Provider) node).getExplorerManager();
} else {
Parent parent = node.getParent();
if (parent == null) {
System.out.println(node.getScene().getWindow().getClass());
return null;
} else {
return find(parent);
}
}
}
}