From fe7daed3fca8913f4341d7efc723b905c8f33a4e Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 16 Jun 2016 17:12:10 -0400 Subject: [PATCH] fix bugs, validate ammount spinner,a dd View in Timeine action to a few more palces --- .../datamodel/BlackboardArtifactNode.java | 4 +- .../autopsy/datamodel/DirectoryNode.java | 4 +- .../sleuthkit/autopsy/datamodel/FileNode.java | 2 +- .../timeline/ShowInTimelineDialog.java | 37 ++++++++++- .../actions/ViewFileInTimelineAction.java | 19 ++++-- .../timeline/explorernodes/EventNode.java | 62 +++++++++++++++++-- .../timeline/ui/listvew/ListTimeline.java | 2 +- .../timeline/ui/listvew/ListViewPane.java | 8 ++- 8 files changed, 120 insertions(+), 18 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 223758fbc4..f27d3f94a8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -130,7 +130,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode { try { AbstractFile c = findLinked(artifact); if (c != null) { - actionsList.add(new ViewFileInTimelineAction(c, false)); + actionsList.add(ViewFileInTimelineAction.createViewFileAction(c)); } } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS @@ -140,7 +140,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode { //if this artifact has associated content, add the action to view the content in the timeline AbstractFile file = getLookup().lookup(AbstractFile.class); if (null != file) { - actionsList.add(new ViewFileInTimelineAction(file, true)); + actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction(file)); } return actionsList.toArray(new Action[actionsList.size()]); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java index a96a1ba662..f15eefd22c 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java @@ -27,6 +27,7 @@ import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; +import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Directory; import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; @@ -80,12 +81,13 @@ public class DirectoryNode extends AbstractFsContentNode { actions.add(null); // creates a menu separator } actions.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "DirectoryNode.viewInNewWin.text"), this)); + actions.add(ViewFileInTimelineAction.createViewFileAction(getContent())); actions.add(null); // creates a menu separator actions.add(ExtractAction.getInstance()); actions.add(null); // creates a menu separator actions.add(AddContentTagAction.getInstance()); actions.addAll(ContextMenuExtensionPoint.getActions()); - return actions.toArray(new Action[0]); + return actions.toArray(new Action[actions.size()]); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index 0e5754166b..b722e33161 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -89,7 +89,7 @@ public class FileNode extends AbstractFsContentNode { } actionsList.add(new NewWindowViewAction(Bundle.FileNode_getActions_viewInNewWin_text(), this)); actionsList.add(new ExternalViewerAction(Bundle.FileNode_getActions_openInExtViewer_text(), this)); - actionsList.add(new ViewFileInTimelineAction(getContent(), false)); + actionsList.add(ViewFileInTimelineAction.createViewFileAction(getContent())); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java index aac34ca468..7dce1857ad 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java @@ -47,8 +47,11 @@ import javafx.scene.control.TableView; import javafx.scene.image.ImageView; import javafx.scene.layout.VBox; import javafx.stage.Modality; +import javafx.util.StringConverter; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.text.WordUtils; +import org.controlsfx.validation.ValidationSupport; +import org.controlsfx.validation.Validator; import org.joda.time.Interval; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; @@ -106,6 +109,7 @@ final class ShowInTimelineDialog extends Dialog { ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE); + private final ValidationSupport validationSupport; /** * Common Private Constructor @@ -114,6 +118,9 @@ final class ShowInTimelineDialog extends Dialog { * @param eventIDS A List of eventIDs to present to the user to choose * from. */ + @NbBundle.Messages({ + "ShowInTimelineDialog.amountValidator.message=The entered amount must be parsable as a number.", + "ShowInTimelineDialog.eventSelectionValidator.message=You must select an event."}) private ShowInTimelineDialog(TimeLineController controller, List eventIDS) { this.controller = controller; @@ -135,6 +142,17 @@ final class ShowInTimelineDialog extends Dialog { assert amountSpinner != null : "fx:id=\"amountsSpinner\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'."; assert unitComboBox != null : "fx:id=\"unitChoiceBox\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'."; + validationSupport = new ValidationSupport(); + validationSupport.registerValidator(amountSpinner.getEditor(), false, Validator.createPredicateValidator((String value) -> { + try { + Double.parseDouble(value); + return true; + } catch (NumberFormatException ex) { + return false; + } + + }, Bundle.ShowInTimelineDialog_amountValidator_message())); + //configure dialog properties PromptDialogManager.setDialogIcons(this); @@ -148,6 +166,21 @@ final class ShowInTimelineDialog extends Dialog { ///configure dialog controls amountSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1000)); + amountSpinner.getValueFactory().setConverter(new StringConverter() { + @Override + public String toString(Integer object) { + return object.toString(); + } + + @Override + public Integer fromString(String string) { + try { + return Integer.valueOf(string); + } catch (NumberFormatException ex) { + return amountSpinner.getValue(); + } + } + }); unitComboBox.setButtonCell(new ChronoFieldListCell()); unitComboBox.setCellFactory(comboBox -> new ChronoFieldListCell()); @@ -182,6 +215,8 @@ final class ShowInTimelineDialog extends Dialog { chooseEventLabel.setManaged(false); eventTable.getSelectionModel().select(0); + getDialogPane().lookupButton(SHOW).disableProperty().bind(validationSupport.invalidProperty()); + //set result converter that does not require selection. setResultConverter(buttonType -> { if (buttonType == SHOW) { @@ -205,7 +240,7 @@ final class ShowInTimelineDialog extends Dialog { this(controller, controller.getEventsModel().getEventIDsForFile(file, false)); //require selection to enable show button - getDialogPane().lookupButton(SHOW).disableProperty().bind(eventTable.getSelectionModel().selectedItemProperty().isNull()); + getDialogPane().lookupButton(SHOW).disableProperty().bind(validationSupport.invalidProperty().or(eventTable.getSelectionModel().selectedItemProperty().isNull())); //set result converter that uses selection. setResultConverter(buttonType -> { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java index 376d260765..f90837e1f6 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/ViewFileInTimelineAction.java @@ -32,17 +32,24 @@ import org.sleuthkit.datamodel.AbstractFile; public final class ViewFileInTimelineAction extends AbstractAction { private static final long serialVersionUID = 1L; + private final AbstractFile file; - @NbBundle.Messages({"ViewFileInTimelineAction.fileSource.displayName=View File in Timeline... ", - "ViewFileInTimelineAction.artifactSource.displayName=View Source File in Timeline... "}) - public ViewFileInTimelineAction(AbstractFile file, boolean isArtifactSource) { - super(isArtifactSource - ? Bundle.ViewFileInTimelineAction_artifactSource_displayName() - : Bundle.ViewFileInTimelineAction_fileSource_displayName()); + private ViewFileInTimelineAction(AbstractFile file, String displayName) { + super(displayName); this.file = file; } + @NbBundle.Messages({"ViewFileInTimelineAction.viewFile.displayName=View File in Timeline... "}) + public static ViewFileInTimelineAction createViewFileAction(AbstractFile file) { + return new ViewFileInTimelineAction(file, Bundle.ViewFileInTimelineAction_viewFile_displayName()); + } + + @NbBundle.Messages({"ViewFileInTimelineAction.viewSourceFile.displayName=View Source File in Timeline... "}) + public static ViewFileInTimelineAction createViewSourceFileAction(AbstractFile file) { + return new ViewFileInTimelineAction(file, Bundle.ViewFileInTimelineAction_viewSourceFile_displayName()); + } + @Override public void actionPerformed(ActionEvent e) { SystemAction.get(OpenTimelineAction.class).showFileInTimeline(file); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java index ee22cfd295..cd1b12ab1c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.timeline.explorernodes; import java.lang.reflect.InvocationTargetException; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -33,16 +34,19 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; @@ -107,20 +111,70 @@ public class EventNode extends DisplayableItemNode { } @Override + @NbBundle.Messages({ + "EventNode.getAction.errorTitle=Error getting actions", + "EventNode.getAction.linkedFileMessage=There was a problem getting actions for the selected result. " + + " The 'View File in Timeline' action will not be available."}) public Action[] getActions(boolean context) { Action[] superActions = super.getActions(context); List actionsList = new ArrayList<>(); actionsList.addAll(Arrays.asList(superActions)); - final Content content = getLookup().lookup(Content.class); - final BlackboardArtifact artifact = getLookup().lookup(BlackboardArtifact.class); + final AbstractFile sourceFile = getLookup().lookup(AbstractFile.class); - final List factoryActions = DataModelActionsFactory.getActions(content, artifact != null); + /* + * if this event is derived from an artifact, add actions to view the + * source file and a "linked" file, if present. + */ + final BlackboardArtifact artifact = getLookup().lookup(BlackboardArtifact.class); + if (artifact != null) { + try { + AbstractFile linkedfile = findLinked(artifact); + if (linkedfile != null) { + actionsList.add(ViewFileInTimelineAction.createViewFileAction(linkedfile)); + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS + MessageNotifyUtil.Notify.error(Bundle.EventNode_getAction_errorTitle(), Bundle.EventNode_getAction_linkedFileMessage()); + } + + //if this event has associated content, add the action to view the content in the timeline + if (null != sourceFile) { + actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction(sourceFile)); + } + } + + //get default actions for the file + final List factoryActions = DataModelActionsFactory.getActions(sourceFile, artifact != null); actionsList.addAll(factoryActions); return actionsList.toArray(new Action[actionsList.size()]); } + /** + * this code started as a cut and past of + * DataResultFilterNode.GetPopupActionsDisplayableItemNodeVisitor.findLinked(BlackboardArtifactNode + * ba) + * + * + * @param artifact + * + * @return + */ + static private AbstractFile findLinked(BlackboardArtifact artifact) throws TskCoreException { + + BlackboardAttribute pathIDAttribute = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH_ID)); + + if (pathIDAttribute != null) { + long contentID = pathIDAttribute.getValueLong(); + if (contentID != -1) { + return artifact.getSleuthkitCase().getAbstractFileById(contentID); + } + } + + return null; + } + @Override public boolean isLeafTypeNode() { return true; 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 2affb8e7e2..26a8de053f 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java @@ -356,7 +356,7 @@ class ListTimeline extends BorderPane { */ private void scrollTo(Integer index) { if (visibleEvents.contains(table.getItems().get(index)) == false) { - table.scrollTo(index); + table.scrollTo(index - visibleEvents.size() / 2); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java index b9cc2f4c6b..c9c2759444 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java @@ -111,9 +111,13 @@ public class ListViewPane extends AbstractTimeLineView { FilteredEventsModel eventsModel = getEventsModel(); + Set selectedEventIDs; + TimeLineController controller = getController(); //grab the currently selected event - Set selectedEventIDs = ImmutableSet.copyOf(getController().getSelectedEventIDs()); - + synchronized (controller) { + selectedEventIDs = ImmutableSet.copyOf(controller.getSelectedEventIDs()); + } + //clear the chart and set the time range. resetView(eventsModel.getTimeRange());