Merge branch 'develop' of https://github.com/sleuthkit/autopsy into index_python

This commit is contained in:
Eugene Livis 2015-11-12 11:36:22 -05:00
commit eb291504fe
21 changed files with 628 additions and 371 deletions

View File

@ -23,10 +23,13 @@ FileTypeIdGlobalSettingsPanel.mimeTypeLabel.text=MIME Type
FileTypeIdGlobalSettingsPanel.saveTypeButton.text=Save
FileTypeIdGlobalSettingsPanel.signatureComboBox.rawItem=Bytes (Hex)
FileTypeIdGlobalSettingsPanel.signatureComboBox.asciiItem=String (ASCII)
FileTypeIdGlobalSettingsPanel.offsetComboBox.startItem=Start
FileTypeIdGlobalSettingsPanel.offsetComboBox.endItem=End
FileTypeIdGlobalSettingsPanel.JOptionPane.invalidMIMEType.message=MIME type is required.
FileTypeIdGlobalSettingsPanel.JOptionPane.invalidMIMEType.title=Missing MIME Type
FileTypeIdGlobalSettingsPanel.JOptionPane.invalidSignature.message=Signature is required.
FileTypeIdGlobalSettingsPanel.JOptionPane.invalidSignature.title=Missing Signature
FileTypeIdGlobalSettingsPanel.JOptionPane.invalidOffset.length=Offset must not be smaller than signature size.
FileTypeIdGlobalSettingsPanel.JOptionPane.invalidOffset.message=Offset must be a positive integer.
FileTypeIdGlobalSettingsPanel.JOptionPane.invalidOffset.title=Invalid Offset
FileTypeIdGlobalSettingsPanel.JOptionPane.invalidRawSignatureBytes.message=The signature has one or more invalid hexadecimal digits.
@ -46,4 +49,4 @@ FileTypeIdGlobalSettingsPanel.jLabel2.text=MIME Types:
FileTypeIdGlobalSettingsPanel.jLabel3.text=Autopsy can automatically detect many file types. Add your custom file types here.
FileTypeIdGlobalSettingsPanel.startUp.fileTypeDetectorInitializationException.msg=Error initializing the file type detector.
FileTypeIdIngestModule.startUp.fileTypeDetectorInitializationException.msg=Error initializing the file type detector.
FileTypeIdGlobalSettingsPanel.offsetRelativeToLabel.text=Offset is relative to

View File

@ -51,7 +51,7 @@ class FileType {
*/
FileType(String mimeType, final Signature signature, String filesSetName, boolean alert) {
this.mimeType = mimeType;
this.signature = new Signature(signature.getSignatureBytes(), signature.getOffset(), signature.getType());
this.signature = new Signature(signature.getSignatureBytes(), signature.getOffset(), signature.getType(), signature.isRelativeToStart());
this.interestingFilesSetName = filesSetName;
this.alert = alert;
}
@ -71,7 +71,7 @@ class FileType {
* @return The signature.
*/
Signature getSignature() {
return new Signature(signature.getSignatureBytes(), signature.getOffset(), signature.getType());
return new Signature(signature.getSignatureBytes(), signature.getOffset(), signature.getType(), signature.isRelativeToStart());
}
/**
@ -148,6 +148,7 @@ class FileType {
private final byte[] signatureBytes;
private final long offset;
private final Type type;
private final boolean isRelativeToStart;
/**
* Creates a file signature consisting of a sequence of bytes at a
@ -162,6 +163,7 @@ class FileType {
this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length);
this.offset = offset;
this.type = type;
this.isRelativeToStart = true;
}
/**
@ -175,6 +177,7 @@ class FileType {
this.signatureBytes = signatureString.getBytes(StandardCharsets.US_ASCII);
this.offset = offset;
this.type = Type.ASCII;
this.isRelativeToStart = true;
}
/**
@ -190,6 +193,56 @@ class FileType {
this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length);
this.offset = offset;
this.type = Type.RAW;
this.isRelativeToStart = true;
}
/**
* Creates a file signature consisting of a sequence of bytes at a
* specific offset within a file.
*
* @param signatureBytes The signature bytes.
* @param offset The offset of the signature bytes.
* @param type The type of data in the byte array. Impacts
* how it is displayed to the user in the UI.
* @param isRelativeToStart Determines whether this signature is relative to start.
*/
Signature(final byte[] signatureBytes, long offset, Type type, boolean isRelativeToStart) {
this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length);
this.offset = offset;
this.type = type;
this.isRelativeToStart = isRelativeToStart;
}
/**
* Creates a file signature consisting of an ASCII string at a
* specific offset within a file.
*
* @param signatureString The ASCII string
* @param offset The offset of the signature bytes.
* @param isRelativeToStart Determines whether this signature is relative to start.
*/
Signature(String signatureString, long offset, boolean isRelativeToStart) {
this.signatureBytes = signatureString.getBytes(StandardCharsets.US_ASCII);
this.offset = offset;
this.type = Type.ASCII;
this.isRelativeToStart = isRelativeToStart;
}
/**
* Creates a file signature consisting of a sequence of bytes at a
* specific offset within a file. If bytes correspond to an ASCII
* string, use one of the other constructors so that the string is
* displayed to the user instead of the raw bytes.
*
* @param signatureBytes The signature bytes.
* @param offset The offset of the signature bytes.
* @param isRelativeToStart Determines whether this signature is relative to start.
*/
Signature(final byte[] signatureBytes, long offset, boolean isRelativeToStart) {
this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length);
this.offset = offset;
this.type = Type.RAW;
this.isRelativeToStart = isRelativeToStart;
}
/**
@ -218,6 +271,10 @@ class FileType {
Type getType() {
return type;
}
boolean isRelativeToStart() {
return isRelativeToStart;
}
/**
* Determines whether or not the signature is contained within a given
@ -228,12 +285,15 @@ class FileType {
* @return True or false.
*/
boolean containedIn(final AbstractFile file) {
if (file.getSize() < (offset + signatureBytes.length)) {
long actualOffset = offset;
if(!isRelativeToStart)
actualOffset = file.getSize() - 1 - offset;
if (file.getSize() < (actualOffset + signatureBytes.length)) {
return false; /// too small, can't contain this signature
}
try {
byte[] buffer = new byte[signatureBytes.length];
int bytesRead = file.read(buffer, offset, signatureBytes.length);
int bytesRead = file.read(buffer, actualOffset, signatureBytes.length);
return ((bytesRead == signatureBytes.length) && (Arrays.equals(buffer, signatureBytes)));
} catch (TskCoreException ex) {
/**

View File

@ -49,40 +49,50 @@
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" alignment="0" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="mimeTypeLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="30" max="-2" attributes="0"/>
<Component id="mimeTypeTextField" min="-2" pref="176" max="-2" attributes="0"/>
</Group>
<Component id="postHitCheckBox" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="signatureTypeLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="signatureTypeComboBox" min="-2" pref="176" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="signatureLabel" min="-2" pref="73" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="hexPrefixLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="signatureTextField" min="-2" pref="160" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="offsetLabel" min="-2" pref="71" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="offsetTextField" min="-2" pref="178" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="1" attributes="0">
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
<Component id="filesSetNameLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="filesSetNameTextField" min="-2" pref="182" max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="mimeTypeLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="30" max="-2" attributes="0"/>
<Component id="mimeTypeTextField" min="-2" pref="176" max="-2" attributes="0"/>
</Group>
<Component id="postHitCheckBox" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="signatureTypeLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="signatureTypeComboBox" min="-2" pref="176" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="signatureLabel" min="-2" pref="73" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="hexPrefixLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="signatureTextField" min="-2" pref="160" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="offsetLabel" min="-2" pref="71" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="offsetTextField" min="-2" pref="178" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
</Group>
</Group>
<Group type="102" alignment="1" attributes="0">
<Component id="saveTypeButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="offsetRelativeToLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="offsetRelativeToComboBox" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
<Component id="jLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
@ -107,7 +117,7 @@
<Group type="102" attributes="0">
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="typesScrollPane" pref="177" max="32767" attributes="0"/>
<Component id="typesScrollPane" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="deleteTypeButton" linkSize="2" alignment="3" min="-2" max="-2" attributes="0"/>
@ -138,12 +148,17 @@
</Group>
<Component id="signatureLabel" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="offsetLabel" min="-2" max="-2" attributes="0"/>
<Component id="offsetTextField" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="offsetTextField" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="offsetLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="offsetRelativeToComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="offsetRelativeToLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="16" max="-2" attributes="0"/>
<Component id="postHitCheckBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
@ -350,5 +365,22 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.JComboBox" name="offsetRelativeToComboBox">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="0"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="offsetRelativeToLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties" key="FileTypeIdGlobalSettingsPanel.offsetRelativeToLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View File

@ -52,6 +52,8 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
private static final String RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureComboBox.rawItem");
private static final String ASCII_SIGNATURE_TYPE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureComboBox.asciiItem");
private static final String START_OFFSET_RELATIVE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.offsetComboBox.startItem");
private static final String END_OFFSET_RELATIVE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.offsetComboBox.endItem");
/**
* The list model for the file types list component of this panel is the set
* of MIME types associated with the user-defined file types. A mapping of
@ -88,6 +90,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
private void customizeComponents() {
setFileTypesListModel();
setSignatureTypeComboBoxModel();
setOffsetRealtiveToComboBoxModel();
clearTypeDetailsComponents();
addTypeListSelectionListener();
addTextFieldListeners();
@ -111,6 +114,17 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
signatureTypeComboBox.setModel(sigTypeComboBoxModel);
signatureTypeComboBox.setSelectedItem(FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM);
}
/**
* Sets the model for the signature type combo box.
*/
private void setOffsetRealtiveToComboBoxModel() {
DefaultComboBoxModel<String> offsetRelComboBoxModel = new DefaultComboBoxModel<>();
offsetRelComboBoxModel.addElement(FileTypeIdGlobalSettingsPanel.START_OFFSET_RELATIVE_COMBO_BOX_ITEM);
offsetRelComboBoxModel.addElement(FileTypeIdGlobalSettingsPanel.END_OFFSET_RELATIVE_COMBO_BOX_ITEM);
offsetRelativeToComboBox.setModel(offsetRelComboBoxModel);
offsetRelativeToComboBox.setSelectedItem(FileTypeIdGlobalSettingsPanel.START_OFFSET_RELATIVE_COMBO_BOX_ITEM);
}
/**
* Adds a listener to the types list component so that the components in the
@ -270,6 +284,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
}
}
signatureTextField.setText(signatureBytes);
offsetRelativeToComboBox.setSelectedItem(signature.isRelativeToStart() ? FileTypeIdGlobalSettingsPanel.START_OFFSET_RELATIVE_COMBO_BOX_ITEM : FileTypeIdGlobalSettingsPanel.END_OFFSET_RELATIVE_COMBO_BOX_ITEM);
offsetTextField.setText(Long.toString(signature.getOffset()));
postHitCheckBox.setSelected(fileType.alertOnMatch());
filesSetNameTextField.setEnabled(postHitCheckBox.isSelected());
@ -289,6 +304,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
signatureTypeComboBox.setSelectedItem(FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM);
hexPrefixLabel.setVisible(true);
signatureTextField.setText("0000"); //NON-NLS
offsetRelativeToComboBox.setSelectedItem(FileTypeIdGlobalSettingsPanel.START_OFFSET_RELATIVE_COMBO_BOX_ITEM);
offsetTextField.setText(""); //NON-NLS
postHitCheckBox.setSelected(false);
filesSetNameTextField.setText(""); //NON-NLS
@ -360,6 +376,8 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
jLabel1 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
jLabel3 = new javax.swing.JLabel();
offsetRelativeToComboBox = new javax.swing.JComboBox<String>();
offsetRelativeToLabel = new javax.swing.JLabel();
setMaximumSize(new java.awt.Dimension(500, 300));
setPreferredSize(new java.awt.Dimension(500, 300));
@ -439,6 +457,8 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.jLabel3.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(offsetRelativeToLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.offsetRelativeToLabel.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@ -465,33 +485,40 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(mimeTypeLabel)
.addGap(30, 30, 30)
.addComponent(mimeTypeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 176, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(postHitCheckBox)
.addGroup(layout.createSequentialGroup()
.addComponent(signatureTypeLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(signatureTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 176, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addComponent(signatureLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 73, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(hexPrefixLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(signatureTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 160, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addComponent(offsetLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(offsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 178, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGap(21, 21, 21)
.addComponent(filesSetNameLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(filesSetNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 182, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addComponent(filesSetNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 182, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(mimeTypeLabel)
.addGap(30, 30, 30)
.addComponent(mimeTypeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 176, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(postHitCheckBox)
.addGroup(layout.createSequentialGroup()
.addComponent(signatureTypeLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(signatureTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 176, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addComponent(signatureLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 73, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(hexPrefixLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(signatureTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 160, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addComponent(offsetLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(offsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 178, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addGap(6, 6, 6)))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(saveTypeButton)
.addGap(8, 8, 8))))
.addGap(8, 8, 8))
.addGroup(layout.createSequentialGroup()
.addComponent(offsetRelativeToLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(offsetRelativeToComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))))
.addComponent(jLabel1)
.addComponent(jLabel3))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
@ -509,7 +536,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel2)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(typesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 177, Short.MAX_VALUE)
.addComponent(typesScrollPane)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(deleteTypeButton)
@ -532,11 +559,15 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
.addComponent(hexPrefixLabel)
.addComponent(signatureTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(signatureLabel))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(offsetLabel)
.addComponent(offsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(offsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(offsetLabel))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(offsetRelativeToComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(offsetRelativeToLabel))
.addGap(16, 16, 16)
.addComponent(postHitCheckBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
@ -596,6 +627,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
byte[] signatureBytes;
if (FileType.Signature.Type.RAW == sigType) {
try {
sigString = sigString.replaceAll("\\s", "");
signatureBytes = DatatypeConverter.parseHexBinary(sigString);
} catch (IllegalArgumentException ex) {
JOptionPane.showMessageDialog(null,
@ -612,8 +644,16 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
* Get the offset.
*/
long offset;
boolean isRelativeToStart = offsetRelativeToComboBox.getSelectedItem() == FileTypeIdGlobalSettingsPanel.START_OFFSET_RELATIVE_COMBO_BOX_ITEM;
try {
offset = Long.parseUnsignedLong(offsetTextField.getText());
offset = Long.parseUnsignedLong(offsetTextField.getText());
if(!isRelativeToStart && signatureBytes.length > offset+1) {
JOptionPane.showMessageDialog(null,
NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidOffset.length"),
NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidOffset.title"),
JOptionPane.ERROR_MESSAGE);
return;
}
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(null,
NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidOffset.message"),
@ -640,7 +680,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
/**
* Put it all together and reset the file types list component.
*/
FileType.Signature signature = new FileType.Signature(signatureBytes, offset, sigType);
FileType.Signature signature = new FileType.Signature(signatureBytes, offset, sigType, isRelativeToStart);
FileType fileType = new FileType(typeName, signature, filesSetName, postHitCheckBox.isSelected());
FileType selected = typesList.getSelectedValue();
if (selected != null) {
@ -683,6 +723,8 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
private javax.swing.JTextField mimeTypeTextField;
private javax.swing.JButton newTypeButton;
private javax.swing.JLabel offsetLabel;
private javax.swing.JComboBox<String> offsetRelativeToComboBox;
private javax.swing.JLabel offsetRelativeToLabel;
private javax.swing.JTextField offsetTextField;
private javax.swing.JCheckBox postHitCheckBox;
private javax.swing.JButton saveTypeButton;

View File

@ -13,8 +13,16 @@
<xs:restriction base="stringType">
<xs:whiteSpace value="preserve"/>
</xs:restriction>
</xs:simpleType>
</xs:simpleType>
<xs:complexType name="offsetType">
<xs:simpleContent>
<xs:extension base="xs:nonNegativeInteger">
<xs:attribute name="RelativeToStart" type="xs:boolean" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="sigInterpretationType">
<xs:restriction base="xs:string">
<xs:enumeration value="RAW"/>
@ -25,7 +33,7 @@
<xs:complexType name="signatureType">
<xs:sequence>
<xs:element name="Bytes" type="stringType"/>
<xs:element name="Offset" type="xs:nonNegativeInteger"/>
<xs:element name="Offset" type="offsetType"/>
</xs:sequence>
<xs:attribute name="type" type="sigInterpretationType" use="required"/>
</xs:complexType>

View File

@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.coreutils.XMLUtil;
import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
/**
@ -67,6 +68,7 @@ final class UserDefinedFileTypesManager {
private static final String SIGNATURE_TYPE_ATTRIBUTE = "type"; //NON-NLS
private static final String BYTES_TAG_NAME = "Bytes"; //NON-NLS
private static final String OFFSET_TAG_NAME = "Offset"; //NON-NLS
private static final String RELATIVE_ATTRIBUTE = "RelativeToStart";
private static final String INTERESTING_FILES_SET_TAG_NAME = "InterestingFileSset"; //NON-NLS
private static final String ALERT_ATTRIBUTE = "alert"; //NON-NLS
private static final String ENCODING_FOR_XML_FILE = "UTF-8"; //NON-NLS
@ -376,6 +378,7 @@ final class UserDefinedFileTypesManager {
Element offsetElem = doc.createElement(OFFSET_TAG_NAME);
offsetElem.setTextContent(DatatypeConverter.printLong(signature.getOffset()));
offsetElem.setAttribute(RELATIVE_ATTRIBUTE, String.valueOf(signature.isRelativeToStart()));
signatureElem.appendChild(offsetElem);
signatureElem.setAttribute(SIGNATURE_TYPE_ATTRIBUTE, signature.getType().toString());
@ -485,10 +488,17 @@ final class UserDefinedFileTypesManager {
String sigBytesString = getChildElementTextContent(signatureElem, BYTES_TAG_NAME);
byte[] signatureBytes = DatatypeConverter.parseHexBinary(sigBytesString);
String offsetString = getChildElementTextContent(signatureElem, OFFSET_TAG_NAME);
Element offsetElem = (Element) signatureElem.getElementsByTagName(OFFSET_TAG_NAME).item(0);
String offsetString = offsetElem.getTextContent();
long offset = DatatypeConverter.parseLong(offsetString);
String relativeString = offsetElem.getAttribute(RELATIVE_ATTRIBUTE);
if(relativeString == null || relativeString.equals(""))
return new Signature(signatureBytes, offset, signatureType);
boolean isRelative = DatatypeConverter.parseBoolean(relativeString);
return new Signature(signatureBytes, offset, signatureType);
return new Signature(signatureBytes, offset, signatureType, isRelative);
}
/**
@ -526,11 +536,14 @@ final class UserDefinedFileTypesManager {
* @param elem The parent element.
* @param tagName The tag name of the child element.
*
* @return The text content.
* @return The text content or null if the tag doesn't exist.
*/
private static String getChildElementTextContent(Element elem, String tagName) {
NodeList childElems = elem.getElementsByTagName(tagName);
Element childElem = (Element) childElems.item(0);
Node childNode = childElems.item(0);
if(childNode == null)
return null;
Element childElem = (Element) childNode;
return childElem.getTextContent();
}

View File

@ -32,13 +32,16 @@ import org.sleuthkit.autopsy.timeline.TimeLineController;
*/
//TODO: This and the corresponding imageanalyzer action are identical except for the type of the controller... abstract something! -jm
public class Back extends Action {
private static final Image BACK_IMAGE = new Image("/org/sleuthkit/autopsy/timeline/images/arrow-180.png", 16, 16, true, true, true); // NON-NLS
private final TimeLineController controller;
@NbBundle.Messages({"Back.text=Back",
"Back.longText=Go back to the last view settings."})
public Back(TimeLineController controller) {
super(NbBundle.getMessage(Back.class, "Back.actions.name.text"));
super(Bundle.Back_text());
setLongText(Bundle.Back_longText());
setGraphic(new ImageView(BACK_IMAGE));
setAccelerator(new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN));
this.controller = controller;

View File

@ -1,4 +0,0 @@
Back.actions.name.text=Back
DefaultFilters.action.name.text=apply default filters
Forward.action.name.text=Forward

View File

@ -37,8 +37,9 @@ public class Forward extends Action {
private final TimeLineController controller;
@NbBundle.Messages("Forward.text=Forward")
public Forward(TimeLineController controller) {
super(NbBundle.getMessage(Forward.class, "Forward.action.name.text"));
super(Bundle.Forward_text());
setGraphic(new ImageView(BACK_IMAGE));
setAccelerator(new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN));
this.controller = controller;

View File

@ -32,8 +32,11 @@ public class ResetFilters extends Action {
private FilteredEventsModel eventsModel;
@NbBundle.Messages({"ResetFilters.text=Reset all filters",
"RestFilters.longText=Reset all filters to their default state."})
public ResetFilters(final TimeLineController controller) {
super(NbBundle.getMessage(ResetFilters.class, "DefaultFilters.action.name.text"));
super(Bundle.ResetFilters_text());
setLongText(Bundle.RestFilters_longText());
eventsModel = controller.getEventsModel();
disabledProperty().bind(new BooleanBinding() {
{

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2014-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,48 +18,63 @@
*/
package org.sleuthkit.autopsy.timeline.actions;
import java.awt.Desktop;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.time.LocalDateTime;
import java.util.function.Consumer;
import java.util.logging.Level;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.stage.DirectoryChooser;
import javafx.util.Pair;
import javafx.stage.Modality;
import javafx.stage.StageStyle;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import org.controlsfx.control.HyperlinkLabel;
import org.controlsfx.control.action.Action;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Save a snapshot of the given node as an autopsy report.
*/
public class SaveSnapshotAsReport extends Action {
private static final Image SNAP_SHOT = new Image("org/sleuthkit/autopsy/timeline/images/image.png", 16, 16, true, true);
private static final String HTML_EXT = ".html";
private static final String REPORT_IMAGE_EXTENSION = ".png";
private static final Logger LOGGER = Logger.getLogger(SaveSnapshotAsReport.class.getName());
private static final Image SNAP_SHOT = new Image("org/sleuthkit/autopsy/timeline/images/image.png", 16, 16, true, true); // NON-NLS
private static final String HTML_EXT = ".html"; // NON-NLS
private static final String REPORT_IMAGE_EXTENSION = ".png"; // NON-NLS
private static final ButtonType open = new ButtonType(Bundle.OpenReportAction_DisplayName(), ButtonBar.ButtonData.NO);
private static final ButtonType ok = new ButtonType(ButtonType.OK.getText(), ButtonBar.ButtonData.CANCEL_CLOSE);
@NbBundle.Messages({"SaveSnapshot.action.name.text=Snapshot",
@NbBundle.Messages({"SaveSnapshot.action.name.text=Snapshot Report",
"SaveSnapshot.action.longText=Save a screen capture of the visualization as a report.",
"SaveSnapshot.fileChoose.title.text=Save snapshot to",})
"SaveSnapshot.fileChoose.title.text=Save snapshot to",
"# {0} - report file path",
"SaveSnapShotAsReport.ReportSavedAt=Report saved at [{0}]",
"Timeline.ModuleName=Timeline", "SaveSnapShotAsReport.Success=Success",
"# {0} - uniqueness identifier, local date time at report creation time",
"SaveSnapsHotAsReport.ReportName=timeline-report-{0}",
"SaveSnapShotAsReport.FailedToAddReport=Failed to add snaphot as a report. See log for details",
"# {0} - report name",
"SaveSnapShotAsReport.ErrorWritingReport=Error writing report {0} to disk. See log for details",})
public SaveSnapshotAsReport(TimeLineController controller, Node node) {
super(Bundle.SaveSnapshot_action_name_text());
setLongText(Bundle.SaveSnapshot_action_longText());
@ -67,78 +82,132 @@ public class SaveSnapshotAsReport extends Action {
setEventHandler(new Consumer<ActionEvent>() {
@Override
public void accept(ActionEvent t) {
//choose location/name
DirectoryChooser fileChooser = new DirectoryChooser();
fileChooser.setTitle(Bundle.SaveSnapshot_fileChoose_title_text());
fileChooser.setInitialDirectory(new File(Case.getCurrentCase().getReportDirectory()));
File reportDirectory = fileChooser.showDialog(null);
if (reportDirectory == null) {
return;
}
reportDirectory.mkdir();
String reportName = reportDirectory.getName();
String reportPath = reportDirectory.getPath();
//gather metadata
List<Pair<String, String>> reportMetaData = new ArrayList<>();
reportMetaData.add(new Pair<>("Case", Case.getCurrentCase().getName())); // NON-NLS
public void accept(ActionEvent actioneEvent) {
String escapedLocalDateTime = FileUtil.escapeFileName(LocalDateTime.now().toString());
String reportName = Bundle.SaveSnapsHotAsReport_ReportName(escapedLocalDateTime);
Path reportPath = Paths.get(Case.getCurrentCase().getReportDirectory(), reportName).toAbsolutePath();
File reportHTMLFIle = reportPath.resolve(reportName + HTML_EXT).toFile();
ZoomParams zoomParams = controller.getEventsModel().zoomParametersProperty().get();
reportMetaData.add(new Pair<>("Time Range", zoomParams.getTimeRange().toString())); // NON-NLS
reportMetaData.add(new Pair<>("Description Level of Detail", zoomParams.getDescriptionLOD().getDisplayName())); // NON-NLS
reportMetaData.add(new Pair<>("Event Type Zoom Level", zoomParams.getTypeZoomLevel().getDisplayName())); // NON-NLS
reportMetaData.add(new Pair<>("Filters", zoomParams.getFilter().getHTMLReportString())); // NON-NLS
//save snapshot as png
try {
WritableImage snapshot = node.snapshot(null, null);
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png",
new File(reportPath, reportName + REPORT_IMAGE_EXTENSION)); // NON-NLS
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "failed to write snapshot to disk", ex); // NON-NLS
return;
}
//ensure directory exists and write html file
Files.createDirectories(reportPath);
try (Writer htmlWriter = new FileWriter(reportHTMLFIle)) {
writeHTMLFile(reportName, htmlWriter, zoomParams);
}
//build html string
StringBuilder wrapper = new StringBuilder();
wrapper.append("<html>\n<head>\n\t<title>").append("timeline snapshot").append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n</head>\n<body>\n"); // NON-NLS
wrapper.append("<div id=\"content\">\n<h1>").append(reportDirectory.getName()).append("</h1>\n"); // NON-NLS
wrapper.append("<img src = \"").append(reportDirectory.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
wrapper.append("<table>\n"); // NON-NLS
for (Pair<String, String> pair : reportMetaData) {
wrapper.append("<tr><td>").append(pair.getKey()).append(": </td><td>").append(pair.getValue()).append("</td></tr>\n"); // NON-NLS
}
wrapper.append("</table>\n"); // NON-NLS
wrapper.append("</div>\n</body>\n</html>"); // NON-NLS
File reportHTMLFIle = new File(reportDirectory, reportName + HTML_EXT);
//take snapshot and save in report directory
ImageIO.write(SwingFXUtils.fromFXImage(node.snapshot(null, null), null), "png", // NON-NLS
reportPath.resolve(reportName + REPORT_IMAGE_EXTENSION).toFile()); // NON-NLS
//write html wrapper
try (Writer htmlWriter = new FileWriter(reportHTMLFIle)) {
htmlWriter.write(wrapper.toString());
} catch (FileNotFoundException ex) {
LOGGER.log(Level.WARNING, "failed to open html wrapper file for writing ", ex); // NON-NLS
return;
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "failed to write html wrapper file", ex); // NON-NLS
return;
}
//copy report css
try (InputStream resource = this.getClass().getResourceAsStream("/org/sleuthkit/autopsy/timeline/index.css")) { // NON-NLS
Files.copy(resource, reportPath.resolve("index.css")); // NON-NLS
}
//copy css
try (InputStream resource = this.getClass().getResourceAsStream("/org/sleuthkit/autopsy/timeline/index.css")) { // NON-NLS
Files.copy(resource, Paths.get(reportPath, "index.css")); // NON-NLS
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "failed to copy css file", ex); // NON-NLS
}
//add html file as report to case
try {
Case.getCurrentCase().addReport(reportHTMLFIle.getPath(), Bundle.Timeline_ModuleName(), reportName + HTML_EXT); // NON-NLS
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "failed to add html wrapper as a report", ex); // NON-NLS
new Alert(Alert.AlertType.ERROR, Bundle.SaveSnapShotAsReport_FailedToAddReport()).showAndWait();
}
//add html file as report to case
try {
Case.getCurrentCase().addReport(reportHTMLFIle.getPath(), "Timeline", reportName + HTML_EXT); // NON-NLS
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "failed add html wrapper as a report", ex); // NON-NLS
//create alert to notify user of report location
final Alert alert = new Alert(Alert.AlertType.INFORMATION, null, open, ok);
alert.setTitle(Bundle.SaveSnapshot_action_name_text());
alert.setHeaderText(Bundle.SaveSnapShotAsReport_Success());
alert.initStyle(StageStyle.UTILITY);
alert.initOwner(node.getScene().getWindow());
alert.initModality(Modality.APPLICATION_MODAL);
//make action to open report, and hyperlinklable to invoke action
final OpenReportAction openReportAction = new OpenReportAction(reportHTMLFIle);
HyperlinkLabel hyperlinkLabel = new HyperlinkLabel(Bundle.SaveSnapShotAsReport_ReportSavedAt(reportHTMLFIle.getPath()));
hyperlinkLabel.setOnAction(openReportAction);
alert.getDialogPane().setContent(hyperlinkLabel);
alert.showAndWait().ifPresent(buttonType -> {
if (buttonType == open) {
openReportAction.handle(null);
}
});
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error writing report " + reportPath + " to disk", e); // NON-NLS
new Alert(Alert.AlertType.ERROR, Bundle.SaveSnapShotAsReport_ErrorWritingReport(reportPath)).showAndWait();
}
}
});
}
private static void writeHTMLFile(String reportName, final Writer htmlWriter, ZoomParams zoomParams) throws IOException {
//write html wrapper file
htmlWriter.write("<html>\n<head>\n\t<title>timeline snapshot</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n</head>\n<body>\n"); // NON-NLS
htmlWriter.write("<div id=\"content\">\n<h1>" + reportName + "</h1>\n"); // NON-NLS
//embed snapshot
htmlWriter.write("<img src = \"" + reportName + REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
//write view paramaters
htmlWriter.write("<table>\n"); // NON-NLS
writeTableRow(htmlWriter, "Case", Case.getCurrentCase().getName()); // NON-NLS
writeTableRow(htmlWriter, "Time Range", zoomParams.getTimeRange().toString()); // NON-NLS
writeTableRow(htmlWriter, "Description Level of Detail", zoomParams.getDescriptionLOD().getDisplayName()); // NON-NLS
writeTableRow(htmlWriter, "Event Type Zoom Level", zoomParams.getTypeZoomLevel().getDisplayName()); // NON-NLS
writeTableRow(htmlWriter, "Filters", zoomParams.getFilter().getHTMLReportString()); // NON-NLS
//end table and html
htmlWriter.write("</table>\n"); // NON-NLS
htmlWriter.write("</div>\n</body>\n</html>"); // NON-NLS
}
/**
*
* @param htmlWriter the value of htmlWriter
* @param key the value of Key
* @param value the value of value
*
* @throws IOException
*/
private static void writeTableRow(final Writer htmlWriter, final String key, final String value) throws IOException {
htmlWriter.write("<tr><td>" + key + ": </td><td>" + value + "</td></tr>\n"); // NON-NLS
}
@NbBundle.Messages({"OpenReportAction.DisplayName=Open Report",
"OpenReportAction.NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.",
"OpenReportAction.MessageBoxTitle=Open Report Failure",
"OpenReportAction.NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.",
"OpenReportAction.MissingReportFileMessage=The report file no longer exists.",
"OpenReportAction.ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied."})
private class OpenReportAction extends Action {
OpenReportAction(File reportHTMLFIle) {
super(Bundle.OpenReportAction_DisplayName());
setEventHandler(actionEvent -> {
try {
Desktop.getDesktop().open(reportHTMLFIle);
} catch (IOException ex) {
JOptionPane.showMessageDialog(null,
Bundle.OpenReportAction_NoAssociatedEditorMessage(),
Bundle.OpenReportAction_MessageBoxTitle(),
JOptionPane.ERROR_MESSAGE);
} catch (UnsupportedOperationException ex) {
JOptionPane.showMessageDialog(null,
Bundle.OpenReportAction_NoOpenInEditorSupportMessage(),
Bundle.OpenReportAction_MessageBoxTitle(),
JOptionPane.ERROR_MESSAGE);
} catch (IllegalArgumentException ex) {
JOptionPane.showMessageDialog(null,
Bundle.OpenReportAction_MissingReportFileMessage(),
Bundle.OpenReportAction_MessageBoxTitle(),
JOptionPane.ERROR_MESSAGE);
} catch (SecurityException ex) {
JOptionPane.showMessageDialog(null,
Bundle.OpenReportAction_ReportFileOpenPermissionDeniedMessage(),
Bundle.OpenReportAction_MessageBoxTitle(),
JOptionPane.ERROR_MESSAGE);
}
});
}
}
}

View File

@ -72,13 +72,12 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
* common history context menu items out of derived classes? -jm
*/
public abstract class AbstractVisualizationPane<X, Y, N, C extends XYChart<X, Y> & TimeLineChart<X>> 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());
@NbBundle.Messages("AbstractVisualization.Default_Tooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.")
private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Default_Tooltip_text());
private static final Logger LOGGER = Logger.getLogger(AbstractVisualizationPane.class.getName());
public static Tooltip getDragTooltip() {
return DRAG_TOOLTIP;
public static Tooltip getDefaultTooltip() {
return DEFAULT_TOOLTIP;
}
protected final SimpleBooleanProperty hasEvents = new SimpleBooleanProperty(true);
@ -242,16 +241,16 @@ public abstract class AbstractVisualizationPane<X, Y, N, C extends XYChart<X, Y>
});
TimeLineController.getTimeZone().addListener(invalidationListener);
//show tooltip text in status bar
hoverProperty().addListener((observable, oldActivated, newActivated) -> {
if (newActivated) {
controller.setStatus(DRAG_TOOLTIP.getText());
controller.setStatus(DEFAULT_TOOLTIP.getText());
} else {
controller.setStatus("");
}
});
update();
}

View File

@ -7,68 +7,94 @@
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<fx:root collapsible="false" contentDisplay="RIGHT" maxHeight="-Infinity" maxWidth="-Infinity" text="No Visible Events" type="TitledPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<graphic><HBox>
<children><Region prefWidth="150.0" /><Button fx:id="dismissButton" contentDisplay="GRAPHIC_ONLY" graphicTextGap="0.0" minHeight="16.0" minWidth="16.0" mnemonicParsing="false" prefHeight="16.0" prefWidth="16.0">
<graphic><ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<fx:root alignment="TOP_RIGHT" maxHeight="-Infinity" maxWidth="-Infinity" type="StackPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<TitledPane collapsible="false" contentDisplay="RIGHT" text="No Visible Events">
<content>
<VBox alignment="CENTER_LEFT" spacing="5.0">
<children>
<Label fx:id="noEventsDialogLabel" contentDisplay="RIGHT" graphicTextGap="10.0" text="There are no events visible with the current zoom / filter settings." wrapText="true" GridPane.columnIndex="1" VBox.vgrow="ALWAYS">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
<font>
<Font size="18.0" />
</font>
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
<graphic>
<ImageView fitHeight="32.0" fitWidth="32.0" pickOnBounds="true" preserveRatio="true" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<image>
<Image url="@../images/information.png" />
</image>
<GridPane.margin>
<Insets />
</GridPane.margin>
<HBox.margin>
<Insets />
</HBox.margin>
</ImageView>
</graphic>
</Label>
<Button fx:id="backButton" alignment="BASELINE_LEFT" layoutX="269.0" layoutY="104.0" maxWidth="1.7976931348623157E308" mnemonicParsing="false">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/arrow-180.png" />
</image>
</ImageView>
</graphic>
</Button>
<Button fx:id="zoomButton" alignment="BASELINE_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/magnifier-zoom-out-red.png" />
</image>
</ImageView>
</graphic>
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
<VBox.margin>
<Insets />
</VBox.margin>
</Button>
<Button fx:id="resetFiltersButton" alignment="BASELINE_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="CENTER">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/arrow-circle-double-135.png" />
</image>
</ImageView>
</graphic>
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
<VBox.margin>
<Insets />
</VBox.margin>
</Button>
</children>
<HBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</HBox.margin>
</VBox>
</content>
</TitledPane>
<Button fx:id="dismissButton" contentDisplay="GRAPHIC_ONLY" graphicTextGap="0.0" mnemonicParsing="false" style="-fx-background-color: rgba(0,0,0,0);">
<graphic>
<ImageView fitHeight="20.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/cross-circle.png" />
</image></ImageView>
</graphic></Button>
</children></HBox>
</graphic>
<content>
<VBox alignment="CENTER_RIGHT" fillWidth="false" prefHeight="130.0" prefWidth="259.0" spacing="5.0">
<children><Label fx:id="noEventsDialogLabel" graphicTextGap="10.0" wrapText="true" GridPane.columnIndex="1" VBox.vgrow="ALWAYS">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
<font>
<Font size="14.0" />
</font>
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
<graphic><ImageView fitHeight="32.0" fitWidth="32.0" pickOnBounds="true" preserveRatio="true" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<image>
<Image url="@../images/information.png" />
</image>
<GridPane.margin>
<Insets />
</GridPane.margin>
<HBox.margin>
<Insets />
</HBox.margin></ImageView>
</graphic></Label><Button fx:id="zoomButton" alignment="BASELINE_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<graphic><ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/magnifier-zoom-out-red.png" />
</image></ImageView>
</ImageView>
</graphic>
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
<VBox.margin>
<Insets />
</VBox.margin></Button><Button fx:id="resetFiltersButton" alignment="BASELINE_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="CENTER">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/arrow-circle-double-135.png" />
</image>
</ImageView>
</graphic>
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
<VBox.margin>
<Insets />
</VBox.margin>
</Button>
</children>
<HBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</HBox.margin>
</VBox>
</content>
<StackPane.margin>
<Insets />
</StackPane.margin>
</Button>
</children>
</fx:root>

View File

@ -37,7 +37,6 @@ import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar;
@ -74,6 +73,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport;
import org.sleuthkit.autopsy.timeline.actions.ZoomIn;
@ -537,8 +537,10 @@ final public class VisualizationPanel extends BorderPane {
}
}
private class NoEventsDialog extends TitledPane {
private class NoEventsDialog extends StackPane {
@FXML
private Button backButton;
@FXML
private Button resetFiltersButton;
@FXML
@ -562,13 +564,12 @@ final public class VisualizationPanel extends BorderPane {
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
ActionUtils.configureButton(new ZoomToEvents(controller), zoomButton);
dismissButton.setOnAction(actionEvent -> closeCallback.run());
Action defaultFiltersAction = new ResetFilters(controller);
resetFiltersButton.setOnAction(defaultFiltersAction);
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
resetFiltersButton.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.resetFiltersButton.text")); // NON-NLS
ActionUtils.configureButton(new ZoomToEvents(controller), zoomButton);
ActionUtils.configureButton(new Back(controller), backButton);
ActionUtils.configureButton(new ResetFilters(controller), resetFiltersButton);
}
}

View File

@ -267,7 +267,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
chart.setData(dataSets);
setCenter(chart);
Tooltip.install(chart, getDragTooltip());
Tooltip.install(chart, getDefaultTooltip());
settingsNodes = new ArrayList<>(new CountsViewSettingsPane().getChildrenUnmodifiable());

View File

@ -1,4 +1,3 @@
Timeline.ui.detailview.tooltip.text={0}\nRight-click to remove.\nRight-drag to reposition.
DetailViewPane.loggedTask.name=Update Details
DetailViewPane.loggedTask.preparing=preparing
DetailViewPane.loggedTask.queryDb=querying db

View File

@ -54,8 +54,6 @@ import static javafx.scene.input.KeyCode.PAGE_DOWN;
import static javafx.scene.input.KeyCode.PAGE_UP;
import static javafx.scene.input.KeyCode.UP;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
@ -93,53 +91,92 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventClu
private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
private MultipleSelectionModel<TreeItem<EventBundle<?>>> treeSelectionModel;
private static final double LINE_SCROLL_PERCENTAGE = .10;
private static final double PAGE_SCROLL_PERCENTAGE = .70;
//these three could be injected from fxml but it was causing npe's
private final DateAxis dateAxis = new DateAxis();
private final Axis<EventCluster> verticalAxis = new EventAxis();
private final ScrollBar vertScrollBar = new ScrollBar();
private final Region scrollBarSpacer = new Region();
private MultipleSelectionModel<TreeItem<EventBundle<?>>> treeSelectionModel;
private final ObservableList<EventBundleNodeBase<?, ?, ?>> highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
//private access to barchart data
private final Map<EventType, XYChart.Series<DateTime, EventCluster>> eventTypeToSeriesMap = new ConcurrentHashMap<>();
private final ScrollBar vertScrollBar = new ScrollBar();
private final Region region = new Region();
private final ObservableList<EventBundleNodeBase<?, ?, ?>> highlightedNodes = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
public ObservableList<EventBundle<?>> getEventBundles() {
return chart.getEventBundles();
}
public DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region bottomLeftSpacer) {
super(controller, partPane, contextPane, bottomLeftSpacer);
public DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
super(controller, partPane, contextPane, spacer);
//initialize chart;
chart = new EventDetailsChart(controller, dateAxis, verticalAxis, selectedNodes);
setChartClickHandler();
setChartClickHandler(); //can we push this into chart
chart.setData(dataSets);
setCenter(chart);
chart.setPrefHeight(USE_COMPUTED_SIZE);
settingsNodes = new ArrayList<>(new DetailViewSettingsPane().getChildrenUnmodifiable());
vertScrollBar.setOrientation(Orientation.VERTICAL);
VBox vBox = new VBox();
VBox.setVgrow(vertScrollBar, Priority.ALWAYS);
vBox.getChildren().add(vertScrollBar);
vBox.getChildren().add(region);
setRight(vBox);
//bind layout fo axes and spacers
dateAxis.setTickLabelGap(0);
dateAxis.setAutoRanging(false);
region.minHeightProperty().bind(dateAxis.heightProperty());
vertScrollBar.visibleAmountProperty().bind(chart.heightProperty().multiply(100).divide(chart.maxVScrollProperty()));
requestLayout();
dateAxis.setTickLabelsVisible(false);
dateAxis.getTickMarks().addListener((Observable observable) -> layoutDateLabels());
dateAxis.getTickSpacing().addListener(observable -> layoutDateLabels());
bottomLeftSpacer.minWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
bottomLeftSpacer.prefWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
bottomLeftSpacer.maxWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
scrollBarSpacer.minHeightProperty().bind(dateAxis.heightProperty());
//configure scrollbar
vertScrollBar.setOrientation(Orientation.VERTICAL);
vertScrollBar.maxProperty().bind(chart.maxVScrollProperty().subtract(chart.heightProperty()));
vertScrollBar.visibleAmountProperty().bind(chart.heightProperty());
vertScrollBar.visibleProperty().bind(vertScrollBar.visibleAmountProperty().greaterThanOrEqualTo(0));
VBox.setVgrow(vertScrollBar, Priority.ALWAYS);
setRight(new VBox(vertScrollBar, scrollBarSpacer));
//interpret scroll events to the scrollBar
this.setOnScroll(scrollEvent ->
vertScrollBar.valueProperty().set(clampScroll(vertScrollBar.getValue() - scrollEvent.getDeltaY())));
//request focus for keyboard scrolling
setOnMouseClicked(mouseEvent -> requestFocus());
//interpret scroll related keys to scrollBar
this.setOnKeyPressed((KeyEvent t) -> {
switch (t.getCode()) {
case PAGE_UP:
incrementScrollValue(-PAGE_SCROLL_PERCENTAGE);
t.consume();
break;
case PAGE_DOWN:
incrementScrollValue(PAGE_SCROLL_PERCENTAGE);
t.consume();
break;
case KP_UP:
case UP:
incrementScrollValue(-LINE_SCROLL_PERCENTAGE);
t.consume();
break;
case KP_DOWN:
case DOWN:
incrementScrollValue(LINE_SCROLL_PERCENTAGE);
t.consume();
break;
}
});
//scrollbar value change handler. This forwards changes in scroll bar to chart
this.vertScrollBar.valueProperty().addListener(observable -> chart.setVScroll(vertScrollBar.getValue()));
//maintain highlighted effect on correct nodes
highlightedNodes.addListener((ListChangeListener.Change<? extends EventBundleNodeBase<?, ?, ?>> change) -> {
while (change.next()) {
change.getAddedSubList().forEach(node -> {
node.applyHighlightEffect(true);
@ -149,71 +186,24 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventClu
});
}
});
//request focus for keyboard scrolling
setOnMouseClicked((MouseEvent t) -> {
requestFocus();
});
//These scroll related handlers don't affect any other view or the model, so they are handled internally
//mouse wheel scroll handler
this.onScrollProperty().set((ScrollEvent t) -> {
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() - t.getDeltaY() / 200.0)));
});
this.setOnKeyPressed((KeyEvent t) -> {
switch (t.getCode()) {
case PAGE_UP:
incrementScrollValue(-70);
break;
case PAGE_DOWN:
incrementScrollValue(70);
break;
case KP_UP:
case UP:
incrementScrollValue(-10);
break;
case KP_DOWN:
case DOWN:
incrementScrollValue(10);
break;
}
t.consume();
});
//scrollbar handler
this.vertScrollBar.valueProperty().addListener((o, oldValue, newValue) -> {
chart.setVScroll(newValue.doubleValue() / 100.0);
});
spacer.minWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
spacer.prefWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
spacer.maxWidthProperty().bind(verticalAxis.widthProperty().add(verticalAxis.tickLengthProperty()));
dateAxis.setTickLabelsVisible(false);
dateAxis.getTickMarks().addListener((Observable observable) -> {
layoutDateLabels();
});
dateAxis.getTickSpacing().addListener((Observable observable) -> {
layoutDateLabels();
});
dateAxis.setTickLabelGap(0);
selectedNodes.addListener((Observable observable) -> {
highlightedNodes.clear();
selectedNodes.stream().forEach((tn) -> {
for (EventBundleNodeBase<?, ?, ?> n : chart.getNodes((EventBundleNodeBase<?, ?, ?> t) ->
t.getDescription().equals(tn.getDescription()))) {
highlightedNodes.add(n);
}
});
});
}
private void incrementScrollValue(int factor) {
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() + factor * (chart.getHeight() / chart.maxVScrollProperty().get()))));
private void incrementScrollValue(double factor) {
vertScrollBar.valueProperty().set(clampScroll(vertScrollBar.getValue() + factor * chart.getHeight()));
}
private Double clampScroll(Double value) {
return Math.max(0, Math.min(vertScrollBar.getMax() + 50, value));
}
public void setSelectionModel(MultipleSelectionModel<TreeItem<EventBundle<?>>> selectionModel) {
@ -409,7 +399,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventClu
private SeparatorMenuItem descVisibilitySeparatorMenuItem;
DetailViewSettingsPane() {
FXMLConstructor.construct(this, "DetailViewSettingsPane.fxml"); // NON-NLS
FXMLConstructor.construct(DetailViewSettingsPane.this, "DetailViewSettingsPane.fxml"); // NON-NLS
}
@FXML
@ -472,5 +462,4 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventClu
public Action newHideDescriptionAction(String description, DescriptionLoD descriptionLoD) {
return chart.new HideDescriptionAction(description, descriptionLoD);
}
}

View File

@ -175,7 +175,7 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
* surprisingly large impact on speed of loading the chart
*/
installTooltip();
Tooltip.uninstall(chart, AbstractVisualizationPane.getDragTooltip());
Tooltip.uninstall(chart, AbstractVisualizationPane.getDefaultTooltip());
showHoverControls(true);
toFront();
});
@ -184,7 +184,7 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
if (parentNode != null) {
parentNode.showHoverControls(true);
} else {
Tooltip.install(chart, AbstractVisualizationPane.getDragTooltip());
Tooltip.install(chart, AbstractVisualizationPane.getDefaultTooltip());
}
});

View File

@ -50,6 +50,7 @@ import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
@ -57,7 +58,6 @@ import javafx.scene.control.ContextMenu;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.shape.Line;
import javafx.scene.shape.StrokeLineCap;
@ -68,6 +68,7 @@ import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
@ -97,6 +98,7 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
*/
public final class EventDetailsChart extends XYChart<DateTime, EventCluster> implements TimeLineChart<DateTime> {
private static final String styleSheet = GuideLine.class.getResource("EventsDetailsChart.css").toExternalForm();
private static final Image HIDE = new Image("/org/sleuthkit/autopsy/timeline/images/eye--minus.png"); // NON-NLS
private static final Image SHOW = new Image("/org/sleuthkit/autopsy/timeline/images/eye--plus.png"); // NON-NLS
private static final Image MARKER = new Image("/org/sleuthkit/autopsy/timeline/images/marker.png", 16, 16, true, true, true);
@ -109,6 +111,7 @@ public final class EventDetailsChart extends XYChart<DateTime, EventCluster> imp
private ContextMenu chartContextMenu;
@Override
public ContextMenu getChartContextMenu() {
return chartContextMenu;
}
@ -116,6 +119,7 @@ public final class EventDetailsChart extends XYChart<DateTime, EventCluster> imp
/**
* a user positionable vertical line to help compare events
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private Line guideLine;
/**
@ -193,6 +197,13 @@ public final class EventDetailsChart extends XYChart<DateTime, EventCluster> imp
this.controller = controller;
this.filteredEvents = this.controller.getEventsModel();
sceneProperty().addListener(observable -> {
Scene scene = getScene();
if (scene != null && scene.getStylesheets().contains(styleSheet) == false) {
scene.getStylesheets().add(styleSheet);
}
});
filteredEvents.zoomParametersProperty().addListener(o -> {
clearGuideLine();
clearIntervalSelector();
@ -200,15 +211,14 @@ public final class EventDetailsChart extends XYChart<DateTime, EventCluster> imp
projectionMap.clear();
controller.selectEventIDs(Collections.emptyList());
});
Tooltip.install(this, AbstractVisualizationPane.getDragTooltip());
Tooltip.install(this, AbstractVisualizationPane.getDefaultTooltip());
dateAxis.setAutoRanging(false);
verticalAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm
verticalAxis.setTickLabelsVisible(false);
verticalAxis.setTickMarkVisible(false);
setLegendVisible(false);
setPadding(Insets.EMPTY);
setAlternativeColumnFillVisible(true);
@ -425,12 +435,11 @@ public final class EventDetailsChart extends XYChart<DateTime, EventCluster> imp
return getNodes(x -> true);
}
synchronized void setVScroll(double d) {
final double h = maxY.get() - (getHeight() * .9);
nodeGroup.setTranslateY(-d * h);
synchronized void setVScroll(double vScrollValue) {
nodeGroup.setTranslateY(-vScrollValue);
}
private void clearGuideLine() {
void clearGuideLine() {
getChartChildren().remove(guideLine);
guideLine = null;
}
@ -612,16 +621,10 @@ public final class EventDetailsChart extends XYChart<DateTime, EventCluster> imp
setGraphic(new ImageView(MARKER)); // NON-NLS
setEventHandler(actionEvent -> {
if (guideLine == null) {
guideLine = new GuideLine(0, 0, 0, getHeight(), getXAxis());
guideLine = new GuideLine(EventDetailsChart.this);
guideLine.relocate(sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
guideLine.endYProperty().bind(heightProperty().subtract(getXAxis().heightProperty().subtract(getXAxis().tickLengthProperty())));
getChartChildren().add(guideLine);
guideLine.setOnMouseClicked(mouseEvent -> {
if (mouseEvent.getButton() == MouseButton.SECONDARY) {
clearGuideLine();
mouseEvent.consume();
}
});
} else {
guideLine.relocate(sceneToLocal(clickEvent.getSceneX(), 0).getX(), 0);
}

View File

@ -0,0 +1,7 @@
.guide-line{
-fx-opacity: .5;
-fx-stroke: red;
-fx-stroke-dash-array: 5 5;
-fx-stroke-width: 3;
-fx-cursor: h-resize;
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2014-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,66 +18,69 @@
*/
package org.sleuthkit.autopsy.timeline.ui.detailview;
import javafx.scene.Cursor;
import javafx.scene.chart.Axis;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.input.MouseButton;
import javafx.scene.shape.Line;
import org.joda.time.DateTime;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
/**
*
* Subclass of {@link Line} with appropriate behavior (mouse listeners) to act
* as a visual reference point in the details view.
*/
@NbBundle.Messages({"# {0} - date/time at guideline position",
"GuideLine.tooltip.text={0}\nRight-click to remove.\nDrag to reposition."})
class GuideLine extends Line {
private final Axis<DateTime> dateAxis;
private static final Tooltip CHART_DEFAULT_TOOLTIP = AbstractVisualizationPane.getDefaultTooltip();
private final Tooltip tooltip = new Tooltip();
private final EventDetailsChart chart;
//used across invocations of mouse event handlers to maintain state
private double startLayoutX;
protected Tooltip tooltip;
private double dragStartX = 0;
GuideLine(double startX, double startY, double endX, double endY, Axis<DateTime> axis) {
super(startX, startY, endX, endY);
dateAxis = axis;
setCursor(Cursor.E_RESIZE);
getStrokeDashArray().setAll(5.0, 5.0);
setStroke(Color.RED);
setOpacity(.5);
setStrokeWidth(3);
/**
* @param chart the chart this GuideLine belongs to.
*/
GuideLine(EventDetailsChart chart) {
super(0, 0, 0, 0);
this.chart = chart;
Axis<DateTime> xAxis = chart.getXAxis();
endYProperty().bind(chart.heightProperty().subtract(xAxis.heightProperty().subtract(xAxis.tickLengthProperty())));
setOnMouseEntered((MouseEvent event) -> {
setTooltip();
});
getStyleClass().add("guide-line");
setOnMousePressed((MouseEvent event) -> {
startLayoutX = getLayoutX();
dragStartX = event.getScreenX();
});
setOnMouseDragged((MouseEvent event) -> {
double dX = event.getScreenX() - dragStartX;
relocate(startLayoutX + dX, 0);
});
}
private void setTooltip() {
Tooltip.uninstall(this, tooltip);
tooltip = new Tooltip(
NbBundle.getMessage(this.getClass(), "Timeline.ui.detailview.tooltip.text", formatSpan(getDateTime())));
Tooltip.install(this, tooltip);
tooltip.setOnShowing(showing -> tooltip.setText(Bundle.GuideLine_tooltip_text(getDateTimeAsString())));
//this is a hack to override the tooltip of the enclosing chart.
setOnMouseEntered(entered -> Tooltip.uninstall(chart, CHART_DEFAULT_TOOLTIP));
setOnMouseExited(exited -> Tooltip.install(chart, CHART_DEFAULT_TOOLTIP));
setOnMouseClicked(clickedEvent -> {
if (clickedEvent.getButton() == MouseButton.SECONDARY
&& clickedEvent.isStillSincePress() == false) {
chart.clearGuideLine();
clickedEvent.consume();
}
});
setOnMousePressed(pressedEvent -> {
startLayoutX = getLayoutX();
dragStartX = pressedEvent.getScreenX();
});
setOnMouseDragged(dragEvent -> {
double dX = dragEvent.getScreenX() - dragStartX;
relocate(startLayoutX + dX, 0);
dragEvent.consume();
});
}
private String formatSpan(DateTime date) {
return date.toString(TimeLineController.getZonedFormatter());
private String getDateTimeAsString() {
return chart.getDateTimeForPosition(getLayoutX()).toString(TimeLineController.getZonedFormatter());
}
private DateTime getDateTime() {
return dateAxis.getValueForDisplay(dateAxis.parentToLocal(getLayoutX(), 0).getX());
}
}