mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-17 18:17:43 +00:00
Merge branch 'develop' of https://github.com/sleuthkit/autopsy into index_python
This commit is contained in:
commit
eb291504fe
@ -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
|
||||
|
@ -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) {
|
||||
/**
|
||||
|
@ -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="<String>"/>
|
||||
</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, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -1,4 +0,0 @@
|
||||
Back.actions.name.text=Back
|
||||
DefaultFilters.action.name.text=apply default filters
|
||||
Forward.action.name.text=Forward
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user