Merge branch 'develop' into fine_grain_ingest_cancellation
@ -16,13 +16,25 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="autopsyLogo" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="29" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Component id="editorPanel" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="closeButton" min="-2" pref="73" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jSeparator1" min="-2" pref="5" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="2" attributes="0">
|
||||
<Component id="newCaseButton" alignment="2" min="-2" max="-2" attributes="1"/>
|
||||
<Component id="openRecentButton" alignment="2" min="-2" pref="70" max="-2" attributes="1"/>
|
||||
<Component id="openCaseButton" alignment="2" min="-2" max="-2" attributes="1"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="103" alignment="1" groupAlignment="0" attributes="0">
|
||||
<Component id="createNewLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="openRecentLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="openLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="closeButton" alignment="1" min="-2" pref="73" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
@ -32,176 +44,34 @@
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="autopsyLogo" alignment="0" min="-2" pref="257" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="editorPanel" min="-2" max="-2" attributes="1"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="2" attributes="0">
|
||||
<Component id="newCaseButton" alignment="2" min="-2" pref="56" max="-2" attributes="0"/>
|
||||
<Component id="createNewLabel" alignment="2" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="2" attributes="0">
|
||||
<Component id="openRecentButton" alignment="2" min="-2" pref="70" max="-2" attributes="0"/>
|
||||
<Component id="openRecentLabel" alignment="2" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="2" attributes="0">
|
||||
<Component id="openCaseButton" alignment="2" min="-2" pref="58" max="-2" attributes="0"/>
|
||||
<Component id="openLabel" alignment="2" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="closeButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jSeparator1" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="autopsyLogo" alignment="0" min="-2" pref="257" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JButton" name="closeButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.closeButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Container class="javax.swing.JPanel" name="editorPanel">
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="newCaseButton" min="-2" max="-2" attributes="1"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="createNewLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="openRecentButton" min="-2" pref="70" max="-2" attributes="1"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="openRecentLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="openCaseButton" min="-2" max="-2" attributes="1"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="openLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace pref="60" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="-2" pref="32" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="2" attributes="0">
|
||||
<Component id="createNewLabel" alignment="2" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="newCaseButton" alignment="2" min="-2" pref="56" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="2" attributes="0">
|
||||
<Component id="openRecentLabel" alignment="2" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="openRecentButton" alignment="2" min="-2" pref="70" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="2" attributes="0">
|
||||
<Component id="openLabel" alignment="2" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="openCaseButton" alignment="2" min="-2" pref="58" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace pref="25" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JButton" name="newCaseButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/casemodule/btn_icon_create_new_case.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.newCaseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="null" type="code"/>
|
||||
</Property>
|
||||
<Property name="borderPainted" type="boolean" value="false"/>
|
||||
<Property name="contentAreaFilled" type="boolean" value="false"/>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[70, 70]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="newCaseButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="openRecentButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/casemodule/btn_icon_open_recent.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.openRecentButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="null" type="code"/>
|
||||
</Property>
|
||||
<Property name="borderPainted" type="boolean" value="false"/>
|
||||
<Property name="contentAreaFilled" type="boolean" value="false"/>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[70, 70]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="openRecentButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="createNewLabel">
|
||||
<Properties>
|
||||
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
|
||||
<Font name="Tahoma" size="13" style="0"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.createNewLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="openRecentLabel">
|
||||
<Properties>
|
||||
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
|
||||
<Font name="Tahoma" size="13" style="0"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.openRecentLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="openCaseButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/casemodule/btn_icon_open_existing.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.openCaseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="null" type="code"/>
|
||||
</Property>
|
||||
<Property name="borderPainted" type="boolean" value="false"/>
|
||||
<Property name="contentAreaFilled" type="boolean" value="false"/>
|
||||
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
|
||||
<Insets value="[1, 1, 1, 1]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[70, 70]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="openCaseButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="openLabel">
|
||||
<Properties>
|
||||
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
|
||||
<Font name="Tahoma" size="13" style="0"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.openLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="autopsyLogo">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
@ -215,5 +85,113 @@
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodePost" type="java.lang.String" value="this.autopsyLogo.setText("");"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="newCaseButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/casemodule/btn_icon_create_new_case.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.newCaseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="null" type="code"/>
|
||||
</Property>
|
||||
<Property name="borderPainted" type="boolean" value="false"/>
|
||||
<Property name="contentAreaFilled" type="boolean" value="false"/>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[64, 64]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="newCaseButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="openRecentButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/casemodule/btn_icon_open_recent.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.openRecentButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="null" type="code"/>
|
||||
</Property>
|
||||
<Property name="borderPainted" type="boolean" value="false"/>
|
||||
<Property name="contentAreaFilled" type="boolean" value="false"/>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[64, 64]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="openRecentButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="createNewLabel">
|
||||
<Properties>
|
||||
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
|
||||
<Font name="Tahoma" size="13" style="0"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.createNewLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="openRecentLabel">
|
||||
<Properties>
|
||||
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
|
||||
<Font name="Tahoma" size="13" style="0"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.openRecentLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="openCaseButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/casemodule/btn_icon_open_existing.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.openCaseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="null" type="code"/>
|
||||
</Property>
|
||||
<Property name="borderPainted" type="boolean" value="false"/>
|
||||
<Property name="contentAreaFilled" type="boolean" value="false"/>
|
||||
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
|
||||
<Insets value="[1, 1, 1, 1]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[64, 64]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="openCaseButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="openLabel">
|
||||
<Properties>
|
||||
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
|
||||
<Font name="Tahoma" size="13" style="0"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.openLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="closeButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/Bundle.properties" key="CueBannerPanel.closeButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSeparator" name="jSeparator1">
|
||||
<Properties>
|
||||
<Property name="orientation" type="int" value="1"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
@ -20,6 +20,9 @@
|
||||
package org.sleuthkit.autopsy.casemodule;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Dialog;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import javax.swing.ImageIcon;
|
||||
@ -64,16 +67,16 @@ public class CueBannerPanel extends javax.swing.JPanel {
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
closeButton = new javax.swing.JButton();
|
||||
editorPanel = new javax.swing.JPanel();
|
||||
autopsyLogo = new javax.swing.JLabel();
|
||||
this.autopsyLogo.setText("");
|
||||
newCaseButton = new javax.swing.JButton();
|
||||
openRecentButton = new javax.swing.JButton();
|
||||
createNewLabel = new javax.swing.JLabel();
|
||||
openRecentLabel = new javax.swing.JLabel();
|
||||
openCaseButton = new javax.swing.JButton();
|
||||
openLabel = new javax.swing.JLabel();
|
||||
autopsyLogo = new javax.swing.JLabel();
|
||||
this.autopsyLogo.setText("");
|
||||
closeButton = new javax.swing.JButton();
|
||||
jSeparator1 = new javax.swing.JSeparator();
|
||||
|
||||
closeButton.setText(org.openide.util.NbBundle.getMessage(CueBannerPanel.class, "CueBannerPanel.closeButton.text")); // NOI18N
|
||||
|
||||
@ -82,7 +85,7 @@ public class CueBannerPanel extends javax.swing.JPanel {
|
||||
newCaseButton.setBorder(null);
|
||||
newCaseButton.setBorderPainted(false);
|
||||
newCaseButton.setContentAreaFilled(false);
|
||||
newCaseButton.setPreferredSize(new java.awt.Dimension(70, 70));
|
||||
newCaseButton.setPreferredSize(new java.awt.Dimension(64, 64));
|
||||
newCaseButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
newCaseButtonActionPerformed(evt);
|
||||
@ -94,7 +97,7 @@ public class CueBannerPanel extends javax.swing.JPanel {
|
||||
openRecentButton.setBorder(null);
|
||||
openRecentButton.setBorderPainted(false);
|
||||
openRecentButton.setContentAreaFilled(false);
|
||||
openRecentButton.setPreferredSize(new java.awt.Dimension(70, 70));
|
||||
openRecentButton.setPreferredSize(new java.awt.Dimension(64, 64));
|
||||
openRecentButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
openRecentButtonActionPerformed(evt);
|
||||
@ -113,7 +116,7 @@ public class CueBannerPanel extends javax.swing.JPanel {
|
||||
openCaseButton.setBorderPainted(false);
|
||||
openCaseButton.setContentAreaFilled(false);
|
||||
openCaseButton.setMargin(new java.awt.Insets(1, 1, 1, 1));
|
||||
openCaseButton.setPreferredSize(new java.awt.Dimension(70, 70));
|
||||
openCaseButton.setPreferredSize(new java.awt.Dimension(64, 64));
|
||||
openCaseButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
openCaseButtonActionPerformed(evt);
|
||||
@ -123,46 +126,10 @@ public class CueBannerPanel extends javax.swing.JPanel {
|
||||
openLabel.setFont(openLabel.getFont().deriveFont(Font.PLAIN, 13));
|
||||
openLabel.setText(org.openide.util.NbBundle.getMessage(CueBannerPanel.class, "CueBannerPanel.openLabel.text")); // NOI18N
|
||||
|
||||
javax.swing.GroupLayout editorPanelLayout = new javax.swing.GroupLayout(editorPanel);
|
||||
editorPanel.setLayout(editorPanelLayout);
|
||||
editorPanelLayout.setHorizontalGroup(
|
||||
editorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(editorPanelLayout.createSequentialGroup()
|
||||
.addGroup(editorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(editorPanelLayout.createSequentialGroup()
|
||||
.addComponent(newCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(createNewLabel))
|
||||
.addGroup(editorPanelLayout.createSequentialGroup()
|
||||
.addComponent(openRecentButton, javax.swing.GroupLayout.PREFERRED_SIZE, 70, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(openRecentLabel))
|
||||
.addGroup(editorPanelLayout.createSequentialGroup()
|
||||
.addComponent(openCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(openLabel)))
|
||||
.addContainerGap(60, Short.MAX_VALUE))
|
||||
);
|
||||
editorPanelLayout.setVerticalGroup(
|
||||
editorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(editorPanelLayout.createSequentialGroup()
|
||||
.addGap(32, 32, 32)
|
||||
.addGroup(editorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
|
||||
.addComponent(createNewLabel)
|
||||
.addComponent(newCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 56, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(editorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
|
||||
.addComponent(openRecentLabel)
|
||||
.addComponent(openRecentButton, javax.swing.GroupLayout.PREFERRED_SIZE, 70, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGap(0, 0, Short.MAX_VALUE)
|
||||
.addGroup(editorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
|
||||
.addComponent(openLabel)
|
||||
.addComponent(openCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 58, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addContainerGap(25, Short.MAX_VALUE))
|
||||
);
|
||||
|
||||
autopsyLogo.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/casemodule/welcome_logo.png"))); // NOI18N NON-NLS
|
||||
autopsyLogo.setText(org.openide.util.NbBundle.getMessage(CueBannerPanel.class, "CueBannerPanel.autopsyLogo.text")); // NOI18N
|
||||
jSeparator1.setOrientation(javax.swing.SwingConstants.VERTICAL);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
@ -171,23 +138,44 @@ public class CueBannerPanel extends javax.swing.JPanel {
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(autopsyLogo)
|
||||
.addGap(29, 29, 29)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(editorPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(closeButton, javax.swing.GroupLayout.PREFERRED_SIZE, 73, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addContainerGap())
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 5, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
|
||||
.addComponent(newCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(openRecentButton, javax.swing.GroupLayout.PREFERRED_SIZE, 70, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(openCaseButton, 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.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(createNewLabel)
|
||||
.addComponent(openRecentLabel)
|
||||
.addComponent(openLabel))
|
||||
.addComponent(closeButton, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 73, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGap(15, 15, 15))
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(autopsyLogo, javax.swing.GroupLayout.PREFERRED_SIZE, 257, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(editorPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(closeButton)))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
|
||||
.addComponent(newCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 56, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(createNewLabel))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
|
||||
.addComponent(openRecentButton, javax.swing.GroupLayout.PREFERRED_SIZE, 70, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(openRecentLabel))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
|
||||
.addComponent(openCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 58, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(openLabel))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(closeButton))
|
||||
.addComponent(jSeparator1)
|
||||
.addComponent(autopsyLogo, javax.swing.GroupLayout.PREFERRED_SIZE, 257, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addContainerGap())
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
@ -235,7 +223,7 @@ public class CueBannerPanel extends javax.swing.JPanel {
|
||||
private javax.swing.JLabel autopsyLogo;
|
||||
private javax.swing.JButton closeButton;
|
||||
private javax.swing.JLabel createNewLabel;
|
||||
private javax.swing.JPanel editorPanel;
|
||||
private javax.swing.JSeparator jSeparator1;
|
||||
private javax.swing.JButton newCaseButton;
|
||||
private javax.swing.JButton openCaseButton;
|
||||
private javax.swing.JLabel openLabel;
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 33 KiB |
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2013 Basis Technology Corp.
|
||||
* Copyright 2011-2014 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*s
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -21,22 +21,20 @@ package org.sleuthkit.autopsy.corecomponents;
|
||||
import java.awt.CardLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.logging.Level;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.openide.util.lookup.ServiceProviders;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.AbstractFile.MimeMatchEnum;
|
||||
import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
|
||||
|
||||
/**
|
||||
@ -47,14 +45,15 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
|
||||
})
|
||||
public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer {
|
||||
|
||||
private static final String[] AUDIO_EXTENSIONS = new String[]{".mp3", ".wav", ".wma"}; //NON-NLS
|
||||
private static final Set<String> AUDIO_EXTENSIONS = new TreeSet<>(Arrays.asList(".mp3", ".wav", ".wma")); //NON-NLS
|
||||
private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName());
|
||||
private AbstractFile lastFile;
|
||||
//UI
|
||||
private final MediaViewVideoPanel videoPanel;
|
||||
private final String[] videoExtensions; // get them from the panel
|
||||
private String[] imageExtensions; // use javafx supported
|
||||
private final List<String> supportedMimes;
|
||||
private final SortedSet<String> videoExtensions; // get them from the panel
|
||||
private final SortedSet<String> imageExtensions;
|
||||
private final SortedSet<String> videoMimes;
|
||||
private final SortedSet<String> imageMimes;
|
||||
private final MediaViewImagePanel imagePanel;
|
||||
private boolean videoPanelInited;
|
||||
private boolean imagePanelInited;
|
||||
@ -70,34 +69,24 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
||||
|
||||
// get the right panel for our platform
|
||||
videoPanel = MediaViewVideoPanel.createVideoPanel();
|
||||
videoPanelInited = videoPanel.isInited();
|
||||
videoExtensions = new TreeSet<>(Arrays.asList(videoPanel.getExtensions()));
|
||||
videoMimes = new TreeSet<>(videoPanel.getMimeTypes());
|
||||
|
||||
imagePanel = new MediaViewImagePanel();
|
||||
videoPanelInited = videoPanel.isInited();
|
||||
imagePanelInited = imagePanel.isInited();
|
||||
|
||||
videoExtensions = videoPanel.getExtensions();
|
||||
supportedMimes = videoPanel.getMimeTypes();
|
||||
imageMimes = new TreeSet<>(imagePanel.getMimeTypes());
|
||||
imageExtensions = new TreeSet<>(imagePanel.getExtensions());
|
||||
|
||||
customizeComponents();
|
||||
logger.log(Level.INFO, "Created MediaView instance: " + this); //NON-NLS
|
||||
}
|
||||
|
||||
private void customizeComponents() {
|
||||
//initialize supported image types
|
||||
//TODO use mime-types instead once we have support
|
||||
String[] fxSupportedImagesSuffixes = ImageIO.getReaderFileSuffixes();
|
||||
imageExtensions = new String[fxSupportedImagesSuffixes.length];
|
||||
//logger.log(Level.INFO, "Supported image formats by javafx image viewer: ");
|
||||
for (int i = 0; i < fxSupportedImagesSuffixes.length; ++i) {
|
||||
String suffix = fxSupportedImagesSuffixes[i];
|
||||
//logger.log(Level.INFO, "suffix: " + suffix);
|
||||
imageExtensions[i] = "." + suffix;
|
||||
}
|
||||
|
||||
add(imagePanel, IMAGE_VIEWER_LAYER);
|
||||
add(videoPanel, VIDEO_VIEWER_LAYER);
|
||||
|
||||
switchPanels(false);
|
||||
|
||||
showVideoPanel(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,19 +126,12 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
||||
|
||||
final Dimension dims = DataContentViewerMedia.this.getSize();
|
||||
//logger.info("setting node on media viewer"); //NON-NLS
|
||||
if (imagePanelInited && containsExt(file.getName(), imageExtensions)) {
|
||||
if (imagePanelInited && isImageSupported(file)) {
|
||||
imagePanel.showImageFx(file, dims);
|
||||
this.switchPanels(false);
|
||||
} else if (imagePanelInited && ImageUtils.isJpegFileHeader(file)) {
|
||||
|
||||
imagePanel.showImageFx(file, dims);
|
||||
this.switchPanels(false);
|
||||
|
||||
} else if (videoPanelInited
|
||||
&& containsMimeType(selectedNode,supportedMimes)&&(containsExt(file.getName(), videoExtensions) || containsExt(file.getName(), AUDIO_EXTENSIONS))) {
|
||||
this.showVideoPanel(false);
|
||||
} else if (videoPanelInited && isVideoSupported(file)) {
|
||||
videoPanel.setupVideo(file, dims);
|
||||
switchPanels(true);
|
||||
|
||||
this.showVideoPanel(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception while setting node", e); //NON-NLS
|
||||
@ -161,7 +143,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
||||
*
|
||||
* @param showVideo true if video panel, false if image panel
|
||||
*/
|
||||
private void switchPanels(boolean showVideo) {
|
||||
private void showVideoPanel(boolean showVideo) {
|
||||
CardLayout layout = (CardLayout) this.getLayout();
|
||||
if (showVideo) {
|
||||
layout.show(this, VIDEO_VIEWER_LAYER);
|
||||
@ -197,6 +179,57 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
||||
lastFile = null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param file
|
||||
* @return True if a video file that can be displayed
|
||||
*/
|
||||
private boolean isVideoSupported(AbstractFile file) {
|
||||
String name = file.getName().toLowerCase();
|
||||
|
||||
if ((containsExt(name, AUDIO_EXTENSIONS) || containsExt(name, videoExtensions)) &&
|
||||
(!videoMimes.isEmpty() && file.isMimeType(videoMimes) == MimeMatchEnum.TRUE)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param file
|
||||
* @return True if an image file that can be displayed
|
||||
*/
|
||||
private boolean isImageSupported(AbstractFile file) {
|
||||
String name = file.getName().toLowerCase();
|
||||
|
||||
// blackboard
|
||||
if (!imageMimes.isEmpty()) {
|
||||
MimeMatchEnum mimeMatch = file.isMimeType(imageMimes);
|
||||
if (mimeMatch == MimeMatchEnum.TRUE) {
|
||||
return true;
|
||||
}
|
||||
else if (mimeMatch == MimeMatchEnum.FALSE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// extension
|
||||
if (containsExt(name, imageExtensions)) {
|
||||
return true;
|
||||
}
|
||||
// our own signature checks for important types
|
||||
else if (ImageUtils.isJpegFileHeader(file)) {
|
||||
return true;
|
||||
}
|
||||
else if (ImageUtils.isPngFileHeader(file)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//for gstreamer formats, check if initialized first, then
|
||||
//support audio formats, and video formats
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(Node node) {
|
||||
if (node == null) {
|
||||
@ -211,23 +244,15 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
||||
if (file.getSize() == 0) {
|
||||
return false;
|
||||
}
|
||||
String name = file.getName().toLowerCase();
|
||||
|
||||
if (imagePanelInited) {
|
||||
if (containsExt(name, imageExtensions)) {
|
||||
if (isImageSupported(file))
|
||||
return true;
|
||||
}
|
||||
else if (ImageUtils.isJpegFileHeader(file)) {
|
||||
return true;
|
||||
}
|
||||
//for gstreamer formats, check if initialized first, then
|
||||
//support audio formats, and video formats
|
||||
}
|
||||
|
||||
if (videoPanelInited && videoPanel.isInited()) {
|
||||
if ((containsExt(name, AUDIO_EXTENSIONS)
|
||||
|| containsExt(name, videoExtensions))&& containsMimeType(node,supportedMimes)) {
|
||||
if (isVideoSupported(file))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -252,34 +277,12 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
|
||||
|
||||
}
|
||||
|
||||
private static boolean containsExt(String name, String[] exts) {
|
||||
private static boolean containsExt(String name, Set<String> exts) {
|
||||
int extStart = name.lastIndexOf(".");
|
||||
String ext = "";
|
||||
if (extStart != -1) {
|
||||
ext = name.substring(extStart, name.length()).toLowerCase();
|
||||
}
|
||||
return Arrays.asList(exts).contains(ext);
|
||||
}
|
||||
private static boolean containsMimeType(Node node, List<String> mimeTypes) {
|
||||
if (mimeTypes.isEmpty()) {
|
||||
//this should return true for 32 bit autopsy with the gstreamer MimeTypes list being empty,
|
||||
//since mimetype detection is for java fx only. For 64 bit java fx, the code continues for Mimetype detection.
|
||||
return true;
|
||||
}
|
||||
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
|
||||
try {
|
||||
ArrayList<BlackboardAttribute> genInfoAttributes = file.getGenInfoAttributes(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG);
|
||||
if (genInfoAttributes.isEmpty() == false) {
|
||||
for (BlackboardAttribute batt : genInfoAttributes) {
|
||||
if (mimeTypes.contains(batt.getValueString())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
return exts.contains(ext);
|
||||
}
|
||||
}
|
||||
|
@ -80,9 +80,7 @@ import org.sleuthkit.datamodel.TskData;
|
||||
public class FXVideoPanel extends MediaViewVideoPanel {
|
||||
|
||||
private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".mpg", ".mpeg"}; //NON-NLS
|
||||
|
||||
static private final List<String> supportedMimes = Arrays.asList("audio/x-aiff", "video/x-javafx", "video/x-flv", "application/vnd.apple.mpegurl", " audio/mpegurl", "audio/mpeg", "video/mp4", "audio/x-m4a", "video/x-m4v", "audio/x-wav"); //NON-NLS
|
||||
|
||||
private static final List<String> MIMETYPES = Arrays.asList("audio/x-aiff", "video/x-javafx", "video/x-flv", "application/vnd.apple.mpegurl", " audio/mpegurl", "audio/mpeg", "video/mp4", "audio/x-m4a", "video/x-m4v", "audio/x-wav"); //NON-NLS
|
||||
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName());
|
||||
|
||||
private boolean fxInited = false;
|
||||
@ -825,6 +823,6 @@ public class FXVideoPanel extends MediaViewVideoPanel {
|
||||
|
||||
@Override
|
||||
public List<String> getMimeTypes() {
|
||||
return supportedMimes;
|
||||
return MIMETYPES;
|
||||
}
|
||||
}
|
||||
|
@ -52,8 +52,6 @@ import org.gstreamer.elements.RGBDataSink;
|
||||
import org.gstreamer.swing.VideoComponent;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.netbeans.api.progress.ProgressHandleFactory;
|
||||
import org.openide.DialogDisplayer;
|
||||
import org.openide.NotifyDescriptor;
|
||||
import org.openide.util.Cancellable;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
@ -71,8 +69,8 @@ import org.sleuthkit.datamodel.TskData;
|
||||
@ServiceProvider(service = FrameCapture.class)
|
||||
})
|
||||
public class GstVideoPanel extends MediaViewVideoPanel {
|
||||
|
||||
private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS
|
||||
private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS
|
||||
private static final List<String> MIMETYPES = Arrays.asList("video/quicktime", "audio/mpeg", "audio/x-mpeg", "video/mpeg", "video/x-mpeg", "audio/mpeg3", "audio/x-mpeg-3", "video/x-flv", "video/mp4", "audio/x-m4a", "video/x-m4v", "audio/x-wav"); //NON-NLS
|
||||
|
||||
private static final Logger logger = Logger.getLogger(GstVideoPanel.class.getName());
|
||||
private boolean gstInited;
|
||||
@ -88,8 +86,8 @@ public class GstVideoPanel extends MediaViewVideoPanel {
|
||||
private boolean autoTracking = false; // true if the slider is moving automatically
|
||||
private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player
|
||||
private AbstractFile currentFile;
|
||||
private Set<String> badVideoFiles = Collections.synchronizedSet(new HashSet<String>());
|
||||
static private final List<String> supportedMimes = Arrays.asList();
|
||||
private final Set<String> badVideoFiles = Collections.synchronizedSet(new HashSet<String>());
|
||||
|
||||
/**
|
||||
* Creates new form MediaViewVideoPanel
|
||||
*/
|
||||
@ -806,6 +804,6 @@ public class GstVideoPanel extends MediaViewVideoPanel {
|
||||
|
||||
@Override
|
||||
public List<String> getMimeTypes() {
|
||||
return supportedMimes;
|
||||
return MIMETYPES;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ import java.awt.EventQueue;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
@ -33,7 +36,6 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
|
||||
@ -47,26 +49,30 @@ import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
* used with JavaFx image viewer only.
|
||||
*/
|
||||
public class MediaViewImagePanel extends javax.swing.JPanel {
|
||||
|
||||
private JFXPanel fxPanel;
|
||||
private ImageView fxImageView;
|
||||
private static final Logger logger = Logger.getLogger(MediaViewImagePanel.class.getName());
|
||||
private boolean fxInited = false;
|
||||
|
||||
private final List<String> supportedExtensions;
|
||||
static private final List<String> supportedMimes = Arrays.asList("image/jpeg", "image/png", "image/gif", "image/bmp");
|
||||
|
||||
/**
|
||||
* Creates new form MediaViewImagePanel
|
||||
*/
|
||||
public MediaViewImagePanel() {
|
||||
initComponents();
|
||||
|
||||
|
||||
|
||||
fxInited = org.sleuthkit.autopsy.core.Installer.isJavaFxInited();
|
||||
|
||||
|
||||
if (fxInited) {
|
||||
setupFx();
|
||||
}
|
||||
|
||||
supportedExtensions = new ArrayList<>();
|
||||
//logger.log(Level.INFO, "Supported image formats by javafx image viewer: ");
|
||||
for (String suffix : ImageIO.getReaderFileSuffixes()) {
|
||||
//logger.log(Level.INFO, "suffix: " + suffix);
|
||||
supportedExtensions.add("." + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInited() {
|
||||
@ -99,13 +105,8 @@ import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
// setVisible(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
@ -207,6 +208,22 @@ import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* returns supported mime types
|
||||
* @return
|
||||
*/
|
||||
public List<String> getMimeTypes() {
|
||||
return supportedMimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns supported extensions (each starting with .)
|
||||
* @return
|
||||
*/
|
||||
public List<String> getExtensions() {
|
||||
return supportedExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
@ -222,4 +239,4 @@ import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
}
|
@ -196,6 +196,32 @@ public class ImageUtils {
|
||||
return (((fileHeaderBuffer[0] & 0xff) == 0xff) && ((fileHeaderBuffer[1] & 0xff) == 0xd8));
|
||||
}
|
||||
|
||||
public static boolean isPngFileHeader(AbstractFile file) {
|
||||
if (file.getSize() < 10) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] fileHeaderBuffer = new byte[8];
|
||||
int bytesRead;
|
||||
try {
|
||||
bytesRead = file.read(fileHeaderBuffer, 0, 8);
|
||||
} catch (TskCoreException ex) {
|
||||
//ignore if can't read the first few bytes, not an image
|
||||
return false;
|
||||
}
|
||||
if (bytesRead != 8) {
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
* Check for the header. Since Java bytes are signed, we cast them
|
||||
* to an int first.
|
||||
*/
|
||||
return (((fileHeaderBuffer[1] & 0xff) == 0x50) && ((fileHeaderBuffer[2] & 0xff) == 0x4E) &&
|
||||
((fileHeaderBuffer[3] & 0xff) == 0x47) && ((fileHeaderBuffer[4] & 0xff) == 0x0D) &&
|
||||
((fileHeaderBuffer[5] & 0xff) == 0x0A) && ((fileHeaderBuffer[6] & 0xff) == 0x1A) &&
|
||||
((fileHeaderBuffer[7] & 0xff) == 0x0A));
|
||||
}
|
||||
|
||||
|
||||
private static Image generateAndSaveIcon(Content content, int iconSize) {
|
||||
Image icon = null;
|
||||
|
@ -99,8 +99,8 @@ class SampleDataSourceIngestModule implements DataSourceIngestModule {
|
||||
// Get files by creation time.
|
||||
long currentTime = System.currentTimeMillis() / 1000;
|
||||
long minTime = currentTime - (14 * 24 * 60 * 60); // Go back two weeks.
|
||||
List<FsContent> otherFiles = sleuthkitCase.findFilesWhere("crtime > " + minTime);
|
||||
for (FsContent otherFile : otherFiles) {
|
||||
List<AbstractFile> otherFiles = fileManager.findFiles(dataSource, "crtime > " + minTime);
|
||||
for (AbstractFile otherFile : otherFiles) {
|
||||
if (!skipKnownFiles || otherFile.getKnown() != TskData.FileKnown.KNOWN) {
|
||||
++fileCount;
|
||||
}
|
||||
|
@ -39,31 +39,13 @@ final class DataSourceIngestPipeline {
|
||||
this.job = job;
|
||||
|
||||
// Create an ingest module instance from each data source ingest module
|
||||
// template. Put the modules in a map of module class names to module
|
||||
// instances to facilitate loading the modules into the pipeline in the
|
||||
// sequence indicated by the ordered list of module class names that
|
||||
// will be obtained from the data source ingest pipeline configuration.
|
||||
Map<String, DataSourceIngestModuleDecorator> modulesByClass = new HashMap<>();
|
||||
// template.
|
||||
for (IngestModuleTemplate template : moduleTemplates) {
|
||||
if (template.isDataSourceIngestModuleTemplate()) {
|
||||
DataSourceIngestModuleDecorator module = new DataSourceIngestModuleDecorator(template.createDataSourceIngestModule(), template.getModuleName());
|
||||
modulesByClass.put(module.getClassName(), module);
|
||||
modules.add(module);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the ingest modules to the pipeline in the order indicated by the
|
||||
// data source ingest pipeline configuration, adding any additional
|
||||
// modules found in the global lookup, but not mentioned in the
|
||||
// configuration, to the end of the pipeline in arbitrary order.
|
||||
List<String> pipelineConfig = IngestPipelinesConfiguration.getInstance().getDataSourceIngestPipelineConfig();
|
||||
for (String moduleClassName : pipelineConfig) {
|
||||
if (modulesByClass.containsKey(moduleClassName)) {
|
||||
modules.add(modulesByClass.remove(moduleClassName));
|
||||
}
|
||||
}
|
||||
for (DataSourceIngestModuleDecorator module : modulesByClass.values()) {
|
||||
modules.add(module);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
|
@ -37,34 +37,12 @@ final class FileIngestPipeline {
|
||||
|
||||
// Create an ingest module instance from each file ingest module
|
||||
// template.
|
||||
// current code uses the order pased in.
|
||||
// Commented out code relied on the XML configuration file for ordering.
|
||||
//Map<String, FileIngestModuleDecorator> modulesByClass = new HashMap<>();
|
||||
for (IngestModuleTemplate template : moduleTemplates) {
|
||||
if (template.isFileIngestModuleTemplate()) {
|
||||
FileIngestModuleDecorator module = new FileIngestModuleDecorator(template.createFileIngestModule(), template.getModuleName());
|
||||
modules.add(module);
|
||||
//modulesByClass.put(module.getClassName(), module);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the ingest modules to the pipeline in the order indicated by the
|
||||
// data source ingest pipeline configuration, adding any additional
|
||||
// modules found in the global lookup, but not mentioned in the
|
||||
// configuration, to the end of the pipeline in arbitrary order.
|
||||
/*List<String> pipelineConfig = IngestPipelinesConfiguration.getInstance().getFileIngestPipelineConfig();
|
||||
for (String moduleClassName : pipelineConfig) {
|
||||
if (modulesByClass.containsKey(moduleClassName)) {
|
||||
modules.add(modulesByClass.remove(moduleClassName));
|
||||
}
|
||||
else {
|
||||
// @@@ add error message to flag renamed / removed modules
|
||||
}
|
||||
}
|
||||
for (FileIngestModuleDecorator module : modulesByClass.values()) {
|
||||
modules.add(module);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
|
@ -125,9 +125,9 @@ public final class IngestJobConfigurator {
|
||||
ModuleSettings.setConfigSetting(launcherContext, DISABLED_INGEST_MODULES_KEY, makeCommaSeparatedList(disabledModuleNames));
|
||||
|
||||
// Get the process unallocated space flag setting. If the setting does
|
||||
// not exist yet, default it to false.
|
||||
// not exist yet, default it to true.
|
||||
if (ModuleSettings.settingExists(launcherContext, PARSE_UNALLOC_SPACE_KEY) == false) {
|
||||
ModuleSettings.setConfigSetting(launcherContext, PARSE_UNALLOC_SPACE_KEY, "false"); //NON-NLS
|
||||
ModuleSettings.setConfigSetting(launcherContext, PARSE_UNALLOC_SPACE_KEY, "true"); //NON-NLS
|
||||
}
|
||||
boolean processUnallocatedSpace = Boolean.parseBoolean(ModuleSettings.getConfigSetting(launcherContext, PARSE_UNALLOC_SPACE_KEY));
|
||||
|
||||
|
@ -22,6 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.DialogDisplayer;
|
||||
import org.openide.NotifyDescriptor;
|
||||
@ -41,7 +42,7 @@ import org.sleuthkit.autopsy.modules.sevenzip.ArchiveFileExtractorModuleFactory;
|
||||
import org.sleuthkit.autopsy.python.JythonModuleLoader;
|
||||
|
||||
/**
|
||||
* Discovers ingest module factories implemented in Java or Jython.
|
||||
* Discovers and instantiates ingest module factories.
|
||||
*/
|
||||
final class IngestModuleFactoryLoader {
|
||||
|
||||
@ -111,9 +112,13 @@ final class IngestModuleFactoryLoader {
|
||||
}
|
||||
}
|
||||
|
||||
// Add any remaining non-core factories discovered. Order is not
|
||||
// guaranteed!
|
||||
factories.addAll(javaFactoriesByClass.values());
|
||||
// Add any remaining non-core factories discovered. Order with an
|
||||
// alphabetical sort by module display name.
|
||||
TreeMap<String, IngestModuleFactory> javaFactoriesSortedByName = new TreeMap<>();
|
||||
for (IngestModuleFactory factory : javaFactoriesByClass.values()) {
|
||||
javaFactoriesSortedByName.put(factory.getModuleDisplayName(), factory);
|
||||
}
|
||||
factories.addAll(javaFactoriesSortedByName.values());
|
||||
|
||||
// Add any ingest module factories implemented using Jython. Order is
|
||||
// not guaranteed!
|
||||
|
@ -60,6 +60,8 @@ import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
|
||||
import net.sf.sevenzipjbinding.ArchiveFormat;
|
||||
import static net.sf.sevenzipjbinding.ArchiveFormat.RAR;
|
||||
|
||||
/**
|
||||
* 7Zip ingest module extracts supported archives, adds extracted DerivedFiles,
|
||||
@ -260,6 +262,53 @@ public final class SevenZipIngestModule implements FileIngestModule {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check file extension and return appropriate input options for SevenZip.openInArchive()
|
||||
*
|
||||
* @param archiveFile file to check file extension
|
||||
* @return input parameter for SevenZip.openInArchive()
|
||||
*/
|
||||
private ArchiveFormat get7ZipOptions(AbstractFile archiveFile)
|
||||
{
|
||||
// try to get the file type from the BB
|
||||
String detectedFormat = null;
|
||||
try {
|
||||
ArrayList<BlackboardAttribute> attributes = archiveFile.getGenInfoAttributes(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG);
|
||||
for (BlackboardAttribute attribute : attributes) {
|
||||
detectedFormat = attribute.getValueString();
|
||||
break;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Couldn't obtain file attributes for file: " + archiveFile.toString(), ex);
|
||||
}
|
||||
|
||||
if (detectedFormat == null) {
|
||||
logger.log(Level.WARNING, "Could not detect format for file: " + archiveFile); //NON-NLS
|
||||
|
||||
// if we don't have attribute info then use file extension
|
||||
String extension = archiveFile.getNameExtension();
|
||||
if ("rar".equals(extension))
|
||||
{
|
||||
// for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
|
||||
// it will be opened incorrectly when using 7zip's built-in auto-detect functionality
|
||||
return RAR;
|
||||
}
|
||||
|
||||
// Otherwise open the archive using 7zip's built-in auto-detect functionality
|
||||
return null;
|
||||
}
|
||||
else if (detectedFormat.contains("application/x-rar-compressed"))
|
||||
{
|
||||
// for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
|
||||
// it will be opened incorrectly when using 7zip's built-in auto-detect functionality
|
||||
return RAR;
|
||||
}
|
||||
|
||||
// Otherwise open the archive using 7zip's built-in auto-detect functionality
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unpack the file to local folder and return a list of derived files
|
||||
@ -301,8 +350,12 @@ public final class SevenZipIngestModule implements FileIngestModule {
|
||||
boolean progressStarted = false;
|
||||
try {
|
||||
stream = new SevenZipContentReadStream(new ReadContentInputStream(archiveFile));
|
||||
inArchive = SevenZip.openInArchive(null, // autodetect archive type
|
||||
stream);
|
||||
|
||||
// for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
|
||||
// it will be opened incorrectly when using 7zip's built-in auto-detect functionality.
|
||||
// All other archive formats are still opened using 7zip built-in auto-detect functionality.
|
||||
ArchiveFormat options = get7ZipOptions(archiveFile);
|
||||
inArchive = SevenZip.openInArchive(options, stream);
|
||||
|
||||
int numItems = inArchive.getNumberOfItems();
|
||||
logger.log(Level.INFO, "Count of items in archive: {0}: {1}", new Object[]{archiveFile.getName(), numItems}); //NON-NLS
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 284 KiB After Width: | Height: | Size: 361 KiB |
@ -23,8 +23,10 @@ import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.text.NumberFormat;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -92,7 +94,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* </li>
|
||||
* <ul>
|
||||
*/
|
||||
public class TimeLineController {
|
||||
public class TimeLineController {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(TimeLineController.class.getName());
|
||||
|
||||
@ -221,9 +223,9 @@ public class TimeLineController {
|
||||
|
||||
filteredEvents = eventsRepository.getEventsModel();
|
||||
InitialZoomState = new ZoomParams(filteredEvents.getSpanningInterval(),
|
||||
EventTypeZoomLevel.BASE_TYPE,
|
||||
Filter.getDefaultFilter(),
|
||||
DescriptionLOD.SHORT);
|
||||
EventTypeZoomLevel.BASE_TYPE,
|
||||
Filter.getDefaultFilter(),
|
||||
DescriptionLOD.SHORT);
|
||||
historyManager.advance(InitialZoomState);
|
||||
|
||||
//persistent listener instances
|
||||
@ -466,13 +468,30 @@ public class TimeLineController {
|
||||
}
|
||||
}
|
||||
|
||||
synchronized public void pushDescrLOD(DescriptionLOD newLOD) {
|
||||
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
|
||||
if (currentZoom == null) {
|
||||
advance(InitialZoomState.withDescrLOD(newLOD));
|
||||
} else if (currentZoom.hasDescrLOD(newLOD) == false) {
|
||||
advance(currentZoom.withDescrLOD(newLOD));
|
||||
synchronized public boolean pushDescrLOD(DescriptionLOD newLOD) {
|
||||
Map<EventType, Long> eventCounts = filteredEvents.getEventCounts(filteredEvents.getRequestedZoomParamters().get().getTimeRange());
|
||||
final Long count = eventCounts.values().stream().reduce(0l, Long::sum);
|
||||
|
||||
boolean shouldContinue = true;
|
||||
if (newLOD == DescriptionLOD.FULL && count > 10_000) {
|
||||
|
||||
int showConfirmDialog = JOptionPane.showConfirmDialog(mainFrame,
|
||||
"You are about to show details for " + NumberFormat.getInstance().format(count) + " events. This might be very slow or even crash Autopsy.\n\nDo you want to continue?",
|
||||
"",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
|
||||
shouldContinue = (showConfirmDialog == JOptionPane.YES_OPTION);
|
||||
}
|
||||
|
||||
if (shouldContinue) {
|
||||
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
|
||||
if (currentZoom == null) {
|
||||
advance(InitialZoomState.withDescrLOD(newLOD));
|
||||
} else if (currentZoom.hasDescrLOD(newLOD) == false) {
|
||||
advance(currentZoom.withDescrLOD(newLOD));
|
||||
}
|
||||
}
|
||||
return shouldContinue;
|
||||
}
|
||||
|
||||
synchronized public void pushTimeAndType(Interval timeRange, EventTypeZoomLevel typeZoom) {
|
||||
@ -599,35 +618,35 @@ public class TimeLineController {
|
||||
*/
|
||||
synchronized public boolean outOfDatePromptAndRebuild() {
|
||||
return showOutOfDateConfirmation() == JOptionPane.YES_OPTION
|
||||
? rebuildRepo()
|
||||
: false;
|
||||
? rebuildRepo()
|
||||
: false;
|
||||
}
|
||||
|
||||
synchronized int showLastPopulatedWhileIngestingConfirmation() {
|
||||
return JOptionPane.showConfirmDialog(mainFrame,
|
||||
DO_REPOPULATE_MESSAGE,
|
||||
"re populate events?",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE);
|
||||
DO_REPOPULATE_MESSAGE,
|
||||
"re populate events?",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.QUESTION_MESSAGE);
|
||||
|
||||
}
|
||||
|
||||
synchronized int showOutOfDateConfirmation() throws MissingResourceException, HeadlessException {
|
||||
return JOptionPane.showConfirmDialog(mainFrame,
|
||||
NbBundle.getMessage(TimeLineController.class,
|
||||
"Timeline.propChg.confDlg.timelineOOD.msg"),
|
||||
NbBundle.getMessage(TimeLineController.class,
|
||||
"Timeline.propChg.confDlg.timelineOOD.details"),
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
NbBundle.getMessage(TimeLineController.class,
|
||||
"Timeline.propChg.confDlg.timelineOOD.msg"),
|
||||
NbBundle.getMessage(TimeLineController.class,
|
||||
"Timeline.propChg.confDlg.timelineOOD.details"),
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
}
|
||||
|
||||
synchronized int showIngestConfirmation() throws MissingResourceException, HeadlessException {
|
||||
return JOptionPane.showConfirmDialog(mainFrame,
|
||||
NbBundle.getMessage(TimeLineController.class,
|
||||
"Timeline.initTimeline.confDlg.genBeforeIngest.msg"),
|
||||
NbBundle.getMessage(TimeLineController.class,
|
||||
"Timeline.initTimeline.confDlg.genBeforeIngest.details"),
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
NbBundle.getMessage(TimeLineController.class,
|
||||
"Timeline.initTimeline.confDlg.genBeforeIngest.msg"),
|
||||
NbBundle.getMessage(TimeLineController.class,
|
||||
"Timeline.initTimeline.confDlg.genBeforeIngest.details"),
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
}
|
||||
|
||||
private class AutopsyIngestModuleListener implements PropertyChangeListener {
|
||||
@ -670,7 +689,7 @@ public class TimeLineController {
|
||||
}
|
||||
|
||||
@Immutable
|
||||
class AutopsyCaseListener implements PropertyChangeListener {
|
||||
private class AutopsyCaseListener implements PropertyChangeListener {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
|
@ -108,7 +108,7 @@ public class AggregateEvent {
|
||||
return new AggregateEvent(IntervalUtils.span(ag1.span, ag2.span), ag1.getType(), ids, ag1.getDescription(), ag1.lod);
|
||||
}
|
||||
|
||||
DescriptionLOD getLOD() {
|
||||
public DescriptionLOD getLOD() {
|
||||
return lod;
|
||||
}
|
||||
}
|
||||
|
@ -259,4 +259,7 @@ public class FilteredEventsModel {
|
||||
// requestedLOD.set(zCrumb.getDescrLOD());
|
||||
// }
|
||||
// }
|
||||
public DescriptionLOD getDescriptionLOD() {
|
||||
return requestedLOD.get();
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,9 @@ public class RootEventType implements EventType {
|
||||
private static class RootEventTypeHolder {
|
||||
|
||||
private static final RootEventType INSTANCE = new RootEventType();
|
||||
|
||||
private RootEventTypeHolder() {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -76,8 +79,6 @@ public class RootEventType implements EventType {
|
||||
return Arrays.asList(BaseTypes.values());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getIconBase() {
|
||||
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||
|
@ -13,7 +13,6 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
public abstract class AbstractFilter implements Filter {
|
||||
|
||||
private final SimpleBooleanProperty active = new SimpleBooleanProperty(true);
|
||||
|
||||
private final SimpleBooleanProperty disabled = new SimpleBooleanProperty(false);
|
||||
|
||||
@Override
|
||||
@ -42,7 +41,7 @@ public abstract class AbstractFilter implements Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isdisabled() {
|
||||
public boolean isDisabled() {
|
||||
return disabled.get();
|
||||
}
|
||||
|
||||
@ -50,4 +49,7 @@ public abstract class AbstractFilter implements Filter {
|
||||
public String getStringCheckBox() {
|
||||
return "[" + (isActive() ? "x" : " ") + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -76,6 +76,5 @@ public interface Filter {
|
||||
|
||||
SimpleBooleanProperty getDisabledProperty();
|
||||
|
||||
boolean isdisabled();
|
||||
|
||||
boolean isDisabled();
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ public class HideKnownFilter extends AbstractFilter {
|
||||
public HideKnownFilter copyOf() {
|
||||
HideKnownFilter hideKnownFilter = new HideKnownFilter();
|
||||
hideKnownFilter.setActive(isActive());
|
||||
hideKnownFilter.setDisabled(isdisabled());
|
||||
hideKnownFilter.setDisabled(isDisabled());
|
||||
return hideKnownFilter;
|
||||
}
|
||||
|
||||
|
@ -10,15 +10,15 @@ import javafx.collections.ObservableList;
|
||||
|
||||
/** Intersection(And) filter */
|
||||
public class IntersectionFilter extends CompoundFilter {
|
||||
|
||||
|
||||
public IntersectionFilter(ObservableList<Filter> subFilters) {
|
||||
super(subFilters);
|
||||
}
|
||||
|
||||
|
||||
public IntersectionFilter() {
|
||||
super(FXCollections.<Filter>observableArrayList());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public IntersectionFilter copyOf() {
|
||||
IntersectionFilter filter = new IntersectionFilter(FXCollections.observableArrayList(
|
||||
@ -26,20 +26,22 @@ public class IntersectionFilter extends CompoundFilter {
|
||||
.map(Filter::copyOf)
|
||||
.collect(Collectors.toList())));
|
||||
filter.setActive(isActive());
|
||||
filter.setDisabled(isdisabled());
|
||||
filter.setDisabled(isDisabled());
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Intersection";
|
||||
return "Intersection" + getSubFilters().stream()
|
||||
.map(Filter::getDisplayName)
|
||||
.collect(Collectors.joining(",", "[", "]"));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getHTMLReportString() {
|
||||
return getSubFilters().stream().filter(Filter::isActive).map(Filter::getHTMLReportString).collect(Collectors.joining("</li><li>", "<ul><li>", "</li></ul>"));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
@ -49,11 +51,11 @@ public class IntersectionFilter extends CompoundFilter {
|
||||
return false;
|
||||
}
|
||||
final IntersectionFilter other = (IntersectionFilter) obj;
|
||||
|
||||
|
||||
if (isActive() != other.isActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < getSubFilters().size(); i++) {
|
||||
if (getSubFilters().get(i).equals(other.getSubFilters().get(i)) == false) {
|
||||
return false;
|
||||
@ -61,7 +63,7 @@ public class IntersectionFilter extends CompoundFilter {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
|
@ -56,7 +56,7 @@ public class TextFilter extends AbstractFilter {
|
||||
synchronized public TextFilter copyOf() {
|
||||
TextFilter textFilter = new TextFilter(getText());
|
||||
textFilter.setActive(isActive());
|
||||
textFilter.setDisabled(isdisabled());
|
||||
textFilter.setDisabled(isDisabled());
|
||||
return textFilter;
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ public class TypeFilter extends UnionFilter {
|
||||
private TypeFilter(EventType et, boolean recursive) {
|
||||
super(FXCollections.observableArrayList());
|
||||
this.eventType = et;
|
||||
|
||||
|
||||
if (recursive) { // add subfilters for each subtype
|
||||
for (EventType subType : et.getSubTypes()) {
|
||||
this.getSubFilters().add(new TypeFilter(subType));
|
||||
@ -57,11 +57,11 @@ public class TypeFilter extends UnionFilter {
|
||||
public TypeFilter(EventType et) {
|
||||
this(et, true);
|
||||
}
|
||||
|
||||
|
||||
public EventType getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return eventType == RootEventType.getInstance() ? "Event Type Filter" : eventType.getDisplayName();
|
||||
@ -76,21 +76,21 @@ public class TypeFilter extends UnionFilter {
|
||||
public Image getFXImage() {
|
||||
return eventType.getFXImage();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TypeFilter copyOf() {
|
||||
//make a nonrecursive copy of this filter
|
||||
final TypeFilter typeFilter = new TypeFilter(eventType, false);
|
||||
typeFilter.setActive(isActive());
|
||||
typeFilter.setDisabled(isdisabled());
|
||||
typeFilter.setDisabled(isDisabled());
|
||||
//add a copy of each subfilter
|
||||
this.getSubFilters().forEach((Filter t) -> {
|
||||
typeFilter.getSubFilters().add(t.copyOf());
|
||||
});
|
||||
|
||||
|
||||
return typeFilter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getHTMLReportString() {
|
||||
String string = getEventType().getDisplayName() + getStringCheckBox();
|
||||
@ -99,7 +99,7 @@ public class TypeFilter extends UnionFilter {
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
@ -109,15 +109,15 @@ public class TypeFilter extends UnionFilter {
|
||||
return false;
|
||||
}
|
||||
final TypeFilter other = (TypeFilter) obj;
|
||||
|
||||
|
||||
if (isActive() != other.isActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (this.eventType != other.eventType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < getSubFilters().size(); i++) {
|
||||
if (getSubFilters().get(i).equals(other.getSubFilters().get(i)) == false) {
|
||||
return false;
|
||||
@ -125,12 +125,12 @@ public class TypeFilter extends UnionFilter {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 67 * hash + Objects.hashCode(this.eventType);
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/minus-button.png
Normal file
After Width: | Height: | Size: 451 B |
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/plus-button.png
Normal file
After Width: | Height: | Size: 544 B |
@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.ui;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import javafx.beans.InvalidationListener;
|
||||
@ -102,7 +103,7 @@ public abstract class AbstractVisualization<X, Y, N extends Node, C extends XYCh
|
||||
/** @return the list of nodes containing settings widgets to insert into
|
||||
* this visualization's header */
|
||||
protected List<Node> getSettingsNodes() {
|
||||
return settingsNodes;
|
||||
return Collections.unmodifiableList(settingsNodes);
|
||||
}
|
||||
|
||||
/** @param value a value along this visualization's x axis
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import java.lang.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
@ -14,10 +15,13 @@
|
||||
<items>
|
||||
<HBox alignment="CENTER" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<Label text="Mode:">
|
||||
<Label text="Visualization Mode:">
|
||||
<HBox.margin>
|
||||
<Insets right="5.0" />
|
||||
</HBox.margin>
|
||||
<font>
|
||||
<Font name="System Bold" size="14.0" />
|
||||
</font>
|
||||
</Label>
|
||||
|
||||
<org.controlsfx.control.SegmentedButton maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity">
|
||||
@ -30,6 +34,9 @@
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</ToggleButton>
|
||||
<ToggleButton fx:id="detailsToggle" alignment="CENTER_RIGHT" layoutX="74.0" mnemonicParsing="false" selected="false" text="Details">
|
||||
<graphic>
|
||||
@ -39,6 +46,9 @@
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</ToggleButton>
|
||||
</buttons>
|
||||
|
||||
@ -52,7 +62,7 @@
|
||||
</BorderPane.margin>
|
||||
</HBox>
|
||||
<Separator orientation="VERTICAL" />
|
||||
<Button fx:id="snapShotButton" mnemonicParsing="false" text="snapshot">
|
||||
<Button fx:id="snapShotButton" mnemonicParsing="false" text="Screenshot">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
|
@ -16,4 +16,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#this is the label for the vetical axis
|
||||
CountsChartPane.numberOfEvents=Number of Events
|
||||
#this is the message shown in the dialog when the user double clicks a bar at seconds resolution
|
||||
CountsViewPane.detailSwitchMessage=There is no temporal resolution smaller than Seconds.\nWould you like to switch to the Details view instead?
|
||||
#this is the title shown in the dialog when the user double clicks a bar at seconds resolution
|
||||
CountsViewPane.detailSwitchTitle="Switch to Details View?
|
||||
|
@ -53,17 +53,20 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javax.swing.JOptionPane;
|
||||
import org.controlsfx.control.action.ActionGroup;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.joda.time.Seconds;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.ColorUtilities;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
||||
@ -451,20 +454,45 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
||||
} else if (e.getClickCount() >= 2) { //double-click => zoom in time
|
||||
if (interval.toDuration().isLongerThan(Seconds.ONE.toStandardDuration())) {
|
||||
controller.pushTimeRange(interval);
|
||||
} else {
|
||||
|
||||
int showConfirmDialog = JOptionPane.showConfirmDialog(null,
|
||||
NbBundle.getMessage(CountsViewPane.class, "CountsViewPane.detailSwitchMessage"),
|
||||
NbBundle.getMessage(CountsViewPane.class, "CountsViewPane.detailSwitchTitle"), JOptionPane.YES_NO_OPTION);
|
||||
if (showConfirmDialog == JOptionPane.YES_OPTION) {
|
||||
controller.setViewMode(VisualizationMode.DETAIL);
|
||||
}
|
||||
|
||||
/* //I would like to use the JAvafx dialog, but it doesn't
|
||||
* block the ui (because it is embeded in a TopComponent)
|
||||
* -jm
|
||||
*
|
||||
* final Dialogs.CommandLink yes = new
|
||||
* Dialogs.CommandLink("Yes", "switch to Details view");
|
||||
* final Dialogs.CommandLink no = new
|
||||
* Dialogs.CommandLink("No", "return to Counts view with a
|
||||
* resolution of Seconds");
|
||||
* Action choice = Dialogs.create()
|
||||
* .title("Switch to Details View?")
|
||||
* .masthead("There is no temporal resolution smaller than
|
||||
* Seconds.")
|
||||
* .message("Would you like to switch to the Details view
|
||||
* instead?")
|
||||
* .showCommandLinks(Arrays.asList(yes, no));
|
||||
*
|
||||
* if (choice == yes) {
|
||||
* controller.setViewMode(VisualizationMode.DETAIL);
|
||||
* } */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CountsViewSettingsPane extends HBox {
|
||||
private class CountsViewSettingsPane extends HBox {
|
||||
|
||||
@FXML
|
||||
private RadioButton logRadio;
|
||||
|
||||
@FXML
|
||||
private RadioButton sqrtRadio;
|
||||
|
||||
@FXML
|
||||
private RadioButton linearRadio;
|
||||
|
||||
@ -474,15 +502,13 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
||||
@FXML
|
||||
void initialize() {
|
||||
assert logRadio != null : "fx:id=\"logRadio\" was not injected: check your FXML file 'CountsViewSettingsPane.fxml'.";
|
||||
assert sqrtRadio != null : "fx:id=\"sqrtRadio\" was not injected: check your FXML file 'CountsViewSettingsPane.fxml'.";
|
||||
assert linearRadio != null : "fx:id=\"linearRadio\" was not injected: check your FXML file 'CountsViewSettingsPane.fxml'.";
|
||||
logRadio.setSelected(true);
|
||||
scaleGroup.selectedToggleProperty().addListener(observable -> {
|
||||
if (scaleGroup.getSelectedToggle() == linearRadio) {
|
||||
scale.set(ScaleType.LINEAR);
|
||||
} else if (scaleGroup.getSelectedToggle() == sqrtRadio) {
|
||||
scale.set(ScaleType.SQUARE_ROOT);
|
||||
} else if (scaleGroup.getSelectedToggle() == logRadio) {
|
||||
}
|
||||
if (scaleGroup.getSelectedToggle() == logRadio) {
|
||||
scale.set(ScaleType.LOGARITHMIC);
|
||||
}
|
||||
});
|
||||
@ -496,7 +522,6 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
||||
private static enum ScaleType {
|
||||
|
||||
LINEAR(t -> t.doubleValue()),
|
||||
SQUARE_ROOT(t -> Math.sqrt(t)),
|
||||
LOGARITHMIC(t -> Math.log10(t) + 1);
|
||||
|
||||
private final Function<Long, Double> func;
|
||||
|
@ -5,25 +5,24 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<fx:root alignment="CENTER_LEFT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="HBox" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<children><FlowPane alignment="CENTER_LEFT" hgap="5.0" prefWrapLength="250.0" vgap="5.0" HBox.hgrow="ALWAYS">
|
||||
<children><Label minHeight="-Infinity" minWidth="-Infinity" text="Scale: ">
|
||||
<HBox.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</HBox.margin></Label><FlowPane alignment="CENTER_LEFT" hgap="5.0" prefWrapLength="250.0" vgap="5.0">
|
||||
<children><RadioButton fx:id="logRadio" mnemonicParsing="false" selected="true" text="Logarithmic">
|
||||
<toggleGroup>
|
||||
<ToggleGroup fx:id="scaleGroup" />
|
||||
</toggleGroup></RadioButton><RadioButton fx:id="linearRadio" mnemonicParsing="false" text="Linear" toggleGroup="$scaleGroup" /><RadioButton fx:id="sqrtRadio" mnemonicParsing="false" text="Square Root" toggleGroup="$scaleGroup" />
|
||||
</children>
|
||||
<fx:root alignment="CENTER_LEFT" type="HBox" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" spacing="5.0">
|
||||
<children>
|
||||
<Label minHeight="-Infinity" minWidth="-Infinity" text="Scale: ">
|
||||
<HBox.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</HBox.margin>
|
||||
<FlowPane.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</FlowPane.margin></FlowPane>
|
||||
</children>
|
||||
<HBox.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</HBox.margin></FlowPane>
|
||||
</Label>
|
||||
<RadioButton fx:id="logRadio" mnemonicParsing="false" selected="true" text="Logarithmic">
|
||||
<toggleGroup>
|
||||
<ToggleGroup fx:id="scaleGroup" />
|
||||
</toggleGroup>
|
||||
</RadioButton>
|
||||
<RadioButton fx:id="linearRadio" mnemonicParsing="false" text="Linear" toggleGroup="$scaleGroup" />
|
||||
</children>
|
||||
<HBox.margin>
|
||||
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
||||
</HBox.margin>
|
||||
</HBox>
|
||||
</children></fx:root>
|
||||
|
@ -18,36 +18,59 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.ui.detailview;
|
||||
|
||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.OverrunStyle;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.Border;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.BorderStroke;
|
||||
import javafx.scene.layout.BorderStrokeStyle;
|
||||
import javafx.scene.layout.BorderWidths;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.sleuthkit.autopsy.coreutils.ColorUtilities;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
||||
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
|
||||
/** Represents an {@link AggregateEvent} in a {@link EventDetailChart}. */
|
||||
public class AggregateEventNode extends StackPane {
|
||||
|
||||
private final static Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png");
|
||||
private final static Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png");
|
||||
|
||||
private static final CornerRadii CORNER_RADII = new CornerRadii(3);
|
||||
|
||||
/** the border to apply when this node is 'selected' */
|
||||
@ -64,6 +87,9 @@ public class AggregateEventNode extends StackPane {
|
||||
/** The label used to display this node's event's description */
|
||||
private final Label descrLabel = new Label();
|
||||
|
||||
/** The label used to display this node's event count */
|
||||
private final Label countLabel = new Label();
|
||||
|
||||
/** The IamgeView used to show the icon for this node's event's type */
|
||||
private final ImageView eventTypeImageView = new ImageView();
|
||||
|
||||
@ -85,18 +111,53 @@ public class AggregateEventNode extends StackPane {
|
||||
* selected/highlighted state of this node in its parent EventDetailChart */
|
||||
private Background spanFill;
|
||||
|
||||
public AggregateEventNode(final AggregateEvent event, AggregateEventNode parentEventNode) {
|
||||
private final Button plusButton = new Button(null, new ImageView(PLUS)) {
|
||||
{
|
||||
setMinSize(16, 16);
|
||||
setMaxSize(16, 16);
|
||||
setPrefSize(16, 16);
|
||||
}
|
||||
};
|
||||
private final Button minusButton = new Button(null, new ImageView(MINUS)) {
|
||||
{
|
||||
setMinSize(16, 16);
|
||||
setMaxSize(16, 16);
|
||||
setPrefSize(16, 16);
|
||||
}
|
||||
};
|
||||
private final EventDetailChart chart;
|
||||
|
||||
private SimpleObjectProperty<DescriptionLOD> descLOD = new SimpleObjectProperty<>();
|
||||
private DescriptionVisibility descrVis;
|
||||
|
||||
public AggregateEventNode(final AggregateEvent event, AggregateEventNode parentEventNode, EventDetailChart chart) {
|
||||
this.event = event;
|
||||
descLOD.set(event.getLOD());
|
||||
this.parentEventNode = parentEventNode;
|
||||
//set initial properties
|
||||
getChildren().addAll(spanRegion, subNodePane, descrLabel);
|
||||
this.chart = chart;
|
||||
final Region region = new Region();
|
||||
HBox.setHgrow(region, Priority.ALWAYS);
|
||||
final HBox hBox = new HBox(descrLabel, countLabel, region, minusButton, plusButton);
|
||||
hBox.setPrefWidth(USE_COMPUTED_SIZE);
|
||||
hBox.setMinWidth(USE_PREF_SIZE);
|
||||
hBox.setPadding(new Insets(2, 5, 2, 5));
|
||||
hBox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
minusButton.setVisible(false);
|
||||
plusButton.setVisible(false);
|
||||
minusButton.setManaged(false);
|
||||
plusButton.setManaged(false);
|
||||
final BorderPane borderPane = new BorderPane(subNodePane, hBox, null, null, null);
|
||||
BorderPane.setAlignment(subNodePane, Pos.TOP_LEFT);
|
||||
borderPane.setPrefWidth(USE_COMPUTED_SIZE);
|
||||
|
||||
getChildren().addAll(spanRegion, borderPane);
|
||||
|
||||
setAlignment(Pos.TOP_LEFT);
|
||||
setMinHeight(24);
|
||||
minWidthProperty().bind(spanRegion.widthProperty());
|
||||
setPrefHeight(USE_COMPUTED_SIZE);
|
||||
setMaxHeight(USE_PREF_SIZE);
|
||||
setMargin(descrLabel, new Insets(2, 5, 2, 5));
|
||||
|
||||
//set up subnode pane sizing contraints
|
||||
subNodePane.setPrefHeight(USE_COMPUTED_SIZE);
|
||||
@ -113,7 +174,7 @@ public class AggregateEventNode extends StackPane {
|
||||
descrLabel.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
|
||||
|
||||
descrLabel.setMouseTransparent(true);
|
||||
setDescriptionVisibility(DescriptionVisibility.SHOWN);
|
||||
setDescriptionVisibility(chart.getDescrVisibility().get());
|
||||
|
||||
//setup backgrounds
|
||||
final Color evtColor = event.getType().getColor();
|
||||
@ -128,20 +189,49 @@ public class AggregateEventNode extends StackPane {
|
||||
//defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart
|
||||
installTooltip();
|
||||
spanRegion.setEffect(new DropShadow(10, evtColor));
|
||||
minusButton.setVisible(true);
|
||||
plusButton.setVisible(true);
|
||||
minusButton.setManaged(true);
|
||||
plusButton.setManaged(true);
|
||||
toFront();
|
||||
|
||||
});
|
||||
|
||||
setOnMouseExited((MouseEvent e) -> {
|
||||
spanRegion.setEffect(null);
|
||||
minusButton.setVisible(false);
|
||||
plusButton.setVisible(false);
|
||||
minusButton.setManaged(false);
|
||||
plusButton.setManaged(false);
|
||||
|
||||
});
|
||||
|
||||
setOnMouseClicked(new EventMouseHandler());
|
||||
|
||||
plusButton.disableProperty().bind(descLOD.isEqualTo(DescriptionLOD.FULL));
|
||||
minusButton.disableProperty().bind(descLOD.isEqualTo(event.getLOD()));
|
||||
|
||||
plusButton.setOnMouseClicked(e -> {
|
||||
final DescriptionLOD next = descLOD.get().next();
|
||||
if (next != null) {
|
||||
loadSubClusters(next);
|
||||
descLOD.set(next);
|
||||
}
|
||||
});
|
||||
minusButton.setOnMouseClicked(e -> {
|
||||
final DescriptionLOD previous = descLOD.get().previous();
|
||||
if (previous != null) {
|
||||
loadSubClusters(previous);
|
||||
descLOD.set(previous);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void installTooltip() {
|
||||
Tooltip.install(AggregateEventNode.this, new Tooltip(getEvent().getEventIDs().size() + " " + getEvent().getType() + " events\n"
|
||||
+ getEvent().getDescription()
|
||||
+ "\nbetween " + getEvent().getSpan().getStart().toString(TimeLineController.getZonedFormatter())
|
||||
+ "\nand " + getEvent().getSpan().getEnd().toString(TimeLineController.getZonedFormatter())
|
||||
+ "\nright-click to adjust local description zoom."));
|
||||
+ getEvent().getDescription()
|
||||
+ "\nbetween " + getEvent().getSpan().getStart().toString(TimeLineController.getZonedFormatter())
|
||||
+ "\nand " + getEvent().getSpan().getEnd().toString(TimeLineController.getZonedFormatter())));
|
||||
}
|
||||
|
||||
public Pane getSubNodePane() {
|
||||
@ -168,22 +258,34 @@ public class AggregateEventNode extends StackPane {
|
||||
*
|
||||
* @param w the maximum width the description label should have
|
||||
*/
|
||||
public void setDescriptionLabelMaxWidth(double w) {
|
||||
public void setDescriptionWidth(double w) {
|
||||
descrLabel.setMaxWidth(w);
|
||||
}
|
||||
|
||||
/** @param descrVis the level of description that should be displayed */
|
||||
final public void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
||||
final void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
||||
this.descrVis = descrVis;
|
||||
final int size = event.getEventIDs().size();
|
||||
|
||||
switch (descrVis) {
|
||||
case SHOWN:
|
||||
descrLabel.setText(event.getDescription() + " (" + event.getEventIDs().size() + ")");
|
||||
break;
|
||||
|
||||
case COUNT_ONLY:
|
||||
descrLabel.setText("(" + event.getEventIDs().size() + ")");
|
||||
descrLabel.setText("");
|
||||
countLabel.setText(String.valueOf(size));
|
||||
break;
|
||||
case HIDDEN:
|
||||
countLabel.setText("");
|
||||
descrLabel.setText("");
|
||||
break;
|
||||
default:
|
||||
case SHOWN:
|
||||
String description = event.getDescription();
|
||||
description = parentEventNode != null
|
||||
? " ..." + StringUtils.substringAfter(description, parentEventNode.getEvent().getDescription())
|
||||
: description;
|
||||
descrLabel.setText(description);
|
||||
countLabel.setText(((size == 1) ? "" : " (" + size + ")"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,9 +311,9 @@ public class AggregateEventNode extends StackPane {
|
||||
|
||||
if (applied) {
|
||||
descrLabel.setStyle("-fx-font-weight: bold;");
|
||||
spanFill = new Background(new BackgroundFill(getEvent().getType().getColor().deriveColor(0, 1, 1, .5), CORNER_RADII, Insets.EMPTY));
|
||||
spanFill = new Background(new BackgroundFill(getEvent().getType().getColor().deriveColor(0, 1, 1, .3), CORNER_RADII, Insets.EMPTY));
|
||||
spanRegion.setBackground(spanFill);
|
||||
setBackground(new Background(new BackgroundFill(getEvent().getType().getColor().deriveColor(0, 1, 1, .3), CORNER_RADII, Insets.EMPTY)));
|
||||
setBackground(new Background(new BackgroundFill(getEvent().getType().getColor().deriveColor(0, 1, 1, .2), CORNER_RADII, Insets.EMPTY)));
|
||||
} else {
|
||||
descrLabel.setStyle("-fx-font-weight: normal;");
|
||||
spanFill = new Background(new BackgroundFill(getEvent().getType().getColor().deriveColor(0, 1, 1, .1), CORNER_RADII, Insets.EMPTY));
|
||||
@ -220,33 +322,13 @@ public class AggregateEventNode extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
/** set the span background and description label visible or not
|
||||
* (the span background is not visible when this node is displaying
|
||||
* subnodes)
|
||||
* //TODO: move more of the control of subnodes/events here and out
|
||||
* of EventDetail Chart
|
||||
*
|
||||
* @param applied true to set the span fill visible, false to hide it
|
||||
*/
|
||||
void setEventDetailsVisible(boolean b) {
|
||||
if (b) {
|
||||
spanRegion.setBackground(spanFill);
|
||||
} else {
|
||||
spanRegion.setBackground(null);
|
||||
}
|
||||
descrLabel.setVisible(b);
|
||||
}
|
||||
|
||||
String getDisplayedDescription() {
|
||||
return descrLabel.getText();
|
||||
}
|
||||
|
||||
double getLayoutXCompensation() {
|
||||
if (parentEventNode != null) {
|
||||
return parentEventNode.getLayoutXCompensation() + getBoundsInParent().getMinX();
|
||||
} else {
|
||||
return getBoundsInParent().getMinX();
|
||||
}
|
||||
return (parentEventNode != null ? parentEventNode.getLayoutXCompensation() : 0)
|
||||
+ getBoundsInParent().getMinX();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -263,4 +345,88 @@ public class AggregateEventNode extends StackPane {
|
||||
this.contextMenu.set(contextMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* loads sub-clusters at the given Description LOD
|
||||
*
|
||||
* @param newLOD
|
||||
*/
|
||||
private void loadSubClusters(DescriptionLOD newLOD) {
|
||||
getSubNodePane().getChildren().clear();
|
||||
if (newLOD == event.getLOD()) {
|
||||
getSubNodePane().getChildren().clear();
|
||||
chart.setRequiresLayout(true);
|
||||
chart.requestChartLayout();
|
||||
} else {
|
||||
//make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters
|
||||
final Filter combinedFilter = Filter.intersect(new Filter[]{new TextFilter(event.getDescription()),
|
||||
new TypeFilter(event.getType()),
|
||||
chart.getFilteredEvents().filter().get()});
|
||||
|
||||
//make a new end inclusive span (to 'filter' with)
|
||||
final Interval span = event.getSpan().withEndMillis(event.getSpan().getEndMillis() + 1000);
|
||||
|
||||
//make a task to load the subnodes
|
||||
LoggedTask<List<AggregateEventNode>> loggedTask = new LoggedTask<List<AggregateEventNode>>("Load sub events", true) {
|
||||
|
||||
@Override
|
||||
protected List<AggregateEventNode> call() throws Exception {
|
||||
//query for the sub-clusters
|
||||
List<AggregateEvent> aggregatedEvents = chart.getFilteredEvents().getAggregatedEvents(new ZoomParams(span,
|
||||
chart.getFilteredEvents().eventTypeZoom().get(),
|
||||
combinedFilter,
|
||||
newLOD));
|
||||
//for each sub cluster make an AggregateEventNode to visually represent it, and set x-position
|
||||
return aggregatedEvents.stream().map((AggregateEvent t) -> {
|
||||
AggregateEventNode subNode = new AggregateEventNode(t, AggregateEventNode.this, chart);
|
||||
subNode.setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(t.getSpan().getStartMillis())) - getLayoutXCompensation());
|
||||
return subNode;
|
||||
}).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
try {
|
||||
chart.setCursor(Cursor.WAIT);
|
||||
//assign subNodes and request chart layout
|
||||
getSubNodePane().getChildren().setAll(get());
|
||||
setDescriptionVisibility(descrVis);
|
||||
chart.setRequiresLayout(true);
|
||||
chart.requestChartLayout();
|
||||
chart.setCursor(null);
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
Exceptions.printStackTrace(ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//start task
|
||||
chart.getController().monitorTask(loggedTask);
|
||||
}
|
||||
}
|
||||
|
||||
/** event handler used for mouse events on {@link AggregateEventNode}s */
|
||||
private class EventMouseHandler implements EventHandler<MouseEvent> {
|
||||
|
||||
@Override
|
||||
public void handle(MouseEvent t) {
|
||||
if (t.getButton() == MouseButton.PRIMARY) {
|
||||
t.consume();
|
||||
if (t.isShiftDown()) {
|
||||
if (chart.selectedNodes.contains(AggregateEventNode.this) == false) {
|
||||
chart.selectedNodes.add(AggregateEventNode.this);
|
||||
}
|
||||
} else if (t.isShortcutDown()) {
|
||||
chart.selectedNodes.removeAll(AggregateEventNode.this);
|
||||
} else if (t.getClickCount() > 1) {
|
||||
final DescriptionLOD next = descLOD.get().next();
|
||||
if (next != null) {
|
||||
loadSubClusters(next);
|
||||
descLOD.set(next);
|
||||
}
|
||||
} else {
|
||||
chart.selectedNodes.setAll(AggregateEventNode.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.BarChart;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MultipleSelectionModel;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.ScrollBar;
|
||||
@ -60,9 +61,9 @@ import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.joda.time.DateTime;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
||||
@ -359,7 +360,7 @@ public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEve
|
||||
c1.applySelectionEffect(applied);
|
||||
}
|
||||
|
||||
class DetailViewSettingsPane extends HBox {
|
||||
private class DetailViewSettingsPane extends HBox {
|
||||
|
||||
@FXML
|
||||
private RadioButton hiddenRadio;
|
||||
@ -391,6 +392,9 @@ public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEve
|
||||
@FXML
|
||||
private Slider truncateWidthSlider;
|
||||
|
||||
@FXML
|
||||
private Label truncateSliderLabel;
|
||||
|
||||
public DetailViewSettingsPane() {
|
||||
FXMLConstructor.construct(this, "DetailViewSettingsPane.fxml");
|
||||
}
|
||||
@ -404,7 +408,7 @@ public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEve
|
||||
bandByTypeBox.selectedProperty().bindBidirectional(chart.getBandByType());
|
||||
truncateAllBox.selectedProperty().bindBidirectional(chart.getTruncateAll());
|
||||
oneEventPerRowBox.selectedProperty().bindBidirectional(chart.getOneEventPerRow());
|
||||
truncateWidthSlider.disableProperty().bind(truncateAllBox.selectedProperty().not());
|
||||
truncateSliderLabel.disableProperty().bind(truncateAllBox.selectedProperty().not());
|
||||
final InvalidationListener sliderListener = o -> {
|
||||
if (truncateWidthSlider.isValueChanging() == false) {
|
||||
chart.getTruncateWidth().set(truncateWidthSlider.getValue());
|
||||
|
@ -7,48 +7,73 @@
|
||||
|
||||
<fx:root alignment="CENTER_LEFT" spacing="5.0" type="javafx.scene.layout.HBox" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<children>
|
||||
<FlowPane alignment="CENTER_LEFT" hgap="5.0" prefHeight="-1.0" prefWidth="-1.0" prefWrapLength="220.0" vgap="5.0" HBox.hgrow="ALWAYS">
|
||||
<children><Label text="Layout Options:" />
|
||||
<HBox spacing="5.0">
|
||||
<children>
|
||||
<CheckBox fx:id="bandByTypeBox" mnemonicParsing="false" text="Band by Type" />
|
||||
<CheckBox fx:id="oneEventPerRowBox" mnemonicParsing="false" text="One Event Per Row" />
|
||||
</children>
|
||||
<FlowPane.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</FlowPane.margin>
|
||||
</HBox>
|
||||
</children>
|
||||
<HBox.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" />
|
||||
</HBox.margin>
|
||||
</FlowPane>
|
||||
<Separator orientation="VERTICAL" prefHeight="-1.0" />
|
||||
<FlowPane columnHalignment="CENTER" hgap="5.0" prefHeight="-1.0" prefWidth="-1.0" prefWrapLength="350.0" vgap="5.0" HBox.hgrow="ALWAYS">
|
||||
<children>
|
||||
<CheckBox fx:id="truncateAllBox" mnemonicParsing="false" text="Truncate Descriptions to (px):" />
|
||||
<Slider id="truncateAllSlider" fx:id="truncateWidthSlider" blockIncrement="50.0" disable="false" majorTickUnit="150.0" max="500.0" min="50.0" minorTickCount="0" prefHeight="33.0" prefWidth="146.0" showTickLabels="true" showTickMarks="false" value="200.0">
|
||||
<FlowPane.margin>
|
||||
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
||||
</FlowPane.margin></Slider>
|
||||
</children>
|
||||
</FlowPane>
|
||||
<Separator orientation="VERTICAL" prefHeight="-1.0" /><FlowPane alignment="CENTER_LEFT" hgap="5.0" minHeight="-Infinity" minWidth="-Infinity" prefWrapLength="200.0" vgap="5.0" HBox.hgrow="ALWAYS">
|
||||
<children><Label text="Description Visbility:" />
|
||||
<HBox spacing="5.0">
|
||||
<children><RadioButton fx:id="showRadio" mnemonicParsing="false" selected="true" text="Show">
|
||||
<MenuButton mnemonicParsing="false" text="Advanced Layout Options">
|
||||
<items>
|
||||
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="Band by Type">
|
||||
<content>
|
||||
<CheckBox fx:id="bandByTypeBox" mnemonicParsing="false" text="Band by Type">
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</padding></CheckBox>
|
||||
</content>
|
||||
</CustomMenuItem>
|
||||
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="One Event Per Row">
|
||||
<content>
|
||||
<CheckBox fx:id="oneEventPerRowBox" mnemonicParsing="false" text="One Event Per Row">
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</padding></CheckBox>
|
||||
</content>
|
||||
</CustomMenuItem>
|
||||
<SeparatorMenuItem mnemonicParsing="false" />
|
||||
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="Truncate Descriptions ">
|
||||
<content>
|
||||
<CheckBox fx:id="truncateAllBox" mnemonicParsing="false" text="Truncate Descriptions ">
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</padding></CheckBox>
|
||||
</content>
|
||||
</CustomMenuItem>
|
||||
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="max description width (px)">
|
||||
<content>
|
||||
<Label fx:id="truncateSliderLabel" contentDisplay="BOTTOM" text="max description width (px):">
|
||||
<graphic>
|
||||
<Slider id="truncateAllSlider" fx:id="truncateWidthSlider" blockIncrement="50.0" disable="false" majorTickUnit="150.0" max="500.0" min="50.0" minorTickCount="0" prefHeight="33.0" prefWidth="150.0" showTickLabels="true" showTickMarks="false" value="200.0" />
|
||||
</graphic>
|
||||
</Label>
|
||||
</content>
|
||||
</CustomMenuItem>
|
||||
<SeparatorMenuItem mnemonicParsing="false" text="Description Visibility" />
|
||||
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="Show Full Description">
|
||||
<content>
|
||||
<RadioButton fx:id="showRadio" mnemonicParsing="false" selected="true" text="Show Full Description">
|
||||
<toggleGroup>
|
||||
<ToggleGroup fx:id="descrVisibility" />
|
||||
</toggleGroup></RadioButton><RadioButton fx:id="countsRadio" mnemonicParsing="false" text="Counts Only" toggleGroup="$descrVisibility" /><RadioButton fx:id="hiddenRadio" mnemonicParsing="false" text="Hide" toggleGroup="$descrVisibility" />
|
||||
</children>
|
||||
<FlowPane.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</FlowPane.margin>
|
||||
</HBox>
|
||||
</children>
|
||||
<HBox.margin>
|
||||
<Insets />
|
||||
</HBox.margin></FlowPane>
|
||||
</toggleGroup>
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</padding>
|
||||
</RadioButton>
|
||||
</content>
|
||||
</CustomMenuItem>
|
||||
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="Show Counts Only">
|
||||
<content>
|
||||
<RadioButton fx:id="countsRadio" mnemonicParsing="false" text="Show Counts Only" toggleGroup="$descrVisibility">
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</padding></RadioButton>
|
||||
</content>
|
||||
</CustomMenuItem>
|
||||
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="Hide Description">
|
||||
<content>
|
||||
<RadioButton fx:id="hiddenRadio" mnemonicParsing="false" text="Hide Description" toggleGroup="$descrVisibility">
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</padding></RadioButton>
|
||||
</content>
|
||||
</CustomMenuItem>
|
||||
</items>
|
||||
</MenuButton>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
|
@ -27,7 +27,6 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.animation.KeyFrame;
|
||||
@ -54,41 +53,27 @@ import javafx.scene.Node;
|
||||
import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.CustomMenuItem;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Slider;
|
||||
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;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import org.controlsfx.control.action.AbstractAction;
|
||||
import org.controlsfx.control.action.ActionGroup;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
|
||||
/**
|
||||
* Custom implementation of {@link XYChart} to graph events on a horizontal
|
||||
@ -103,7 +88,7 @@ import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
* node to contain each band if we need a place for per band controls.
|
||||
*
|
||||
* //TODO: refactor the projected lines to a separate class. -jm */
|
||||
public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implements TimeLineChart<DateTime> {
|
||||
public final class EventDetailChart extends XYChart<DateTime, AggregateEvent> implements TimeLineChart<DateTime> {
|
||||
|
||||
private static final int PROJECTED_LINE_Y_OFFSET = 5;
|
||||
|
||||
@ -113,14 +98,16 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
* events together during layout */
|
||||
private final SimpleBooleanProperty bandByType = new SimpleBooleanProperty(false);
|
||||
|
||||
// I don't like having these package visible, but it was the easiest way to
|
||||
private ContextMenu chartContextMenu;
|
||||
|
||||
private TimeLineController controller;
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
|
||||
/** how much detail of the description to show in the ui */
|
||||
private final SimpleObjectProperty<DescriptionVisibility> descrVisibility = new SimpleObjectProperty<>(DescriptionVisibility.SHOWN);
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
|
||||
/** a user position-able vertical line to help the compare events */
|
||||
private Line guideLine;
|
||||
@ -171,7 +158,7 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
@GuardedBy(value = "this")
|
||||
private boolean requiresLayout = true;
|
||||
|
||||
private final ObservableList<AggregateEventNode> selectedNodes;
|
||||
final ObservableList<AggregateEventNode> selectedNodes;
|
||||
|
||||
/**
|
||||
* list of series of data added to this chart TODO: replace this with a map
|
||||
@ -262,7 +249,7 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
}
|
||||
}
|
||||
}, new ActionGroup("Zoom History", new Back(controller),
|
||||
new Forward(controller))));
|
||||
new Forward(controller))));
|
||||
chartContextMenu.setAutoHide(true);
|
||||
chartContextMenu.show(EventDetailChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
|
||||
clickEvent.consume();
|
||||
@ -297,7 +284,7 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
});
|
||||
c.getAddedSubList().forEach((AggregateEventNode t) -> {
|
||||
Line line = new Line(dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getEvent().getSpan().getStartMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET,
|
||||
dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getEvent().getSpan().getEndMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET
|
||||
dateAxis.localToParent(dateAxis.getDisplayPosition(new DateTime(t.getEvent().getSpan().getEndMillis(), TimeLineController.getJodaTimeZone())), 0).getX(), dateAxis.getLayoutY() + PROJECTED_LINE_Y_OFFSET
|
||||
);
|
||||
line.setStroke(t.getEvent().getType().getColor().deriveColor(0, 1, 1, .5));
|
||||
line.setStrokeWidth(PROJECTED_LINE_STROKE_WIDTH);
|
||||
@ -326,7 +313,7 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
}
|
||||
|
||||
@Override
|
||||
public final synchronized void setController(TimeLineController controller) {
|
||||
public synchronized void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(this.controller.getEventsModel());
|
||||
}
|
||||
@ -361,7 +348,7 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
* @return the DateTime along the x-axis corresponding to the given x value
|
||||
* (in the space of this {@link EventDetailChart}
|
||||
*/
|
||||
public final DateTime getDateTimeForPosition(double x) {
|
||||
public DateTime getDateTimeForPosition(double x) {
|
||||
return getXAxis().getValueForDisplay(getXAxis().parentToLocal(x, 0).getX());
|
||||
}
|
||||
|
||||
@ -398,8 +385,7 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
final AggregateEvent aggEvent = data.getYValue();
|
||||
AggregateEventNode eventNode = nodeMap.get(aggEvent);
|
||||
if (eventNode == null) {
|
||||
eventNode = new AggregateEventNode(aggEvent, null);
|
||||
eventNode.setOnMouseClicked(new EventMouseHandler(eventNode));
|
||||
eventNode = new AggregateEventNode(aggEvent, null, this);
|
||||
|
||||
eventNode.setLayoutX(getXAxis().getDisplayPosition(new DateTime(aggEvent.getSpan().getStartMillis())));
|
||||
data.setNode(eventNode);
|
||||
@ -566,10 +552,9 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
//size timespan border
|
||||
tlNode.setSpanWidth(span);
|
||||
if (truncateAll.get()) { //if truncate option is selected limit width of description label
|
||||
tlNode.setDescriptionLabelMaxWidth(Math.max(span, truncateWidth.get()));
|
||||
tlNode.setDescriptionWidth(Math.max(span, truncateWidth.get()));
|
||||
} else { //else set it unbounded
|
||||
|
||||
tlNode.setDescriptionLabelMaxWidth(20 + new Text(tlNode.getDisplayedDescription()).getLayoutBounds().getWidth());
|
||||
tlNode.setDescriptionWidth(USE_PREF_SIZE);//20 + new Text(tlNode.getDisplayedDescription()).getLayoutBounds().getWidth());
|
||||
}
|
||||
tlNode.autosize(); //compute size of tlNode based on constraints and event data
|
||||
|
||||
@ -577,7 +562,7 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
double xRight = xPos + tlNode.getWidth();
|
||||
|
||||
//get the height of the node
|
||||
final double h = layoutNodesResultHeight == 0 ? tlNode.getHeight() : layoutNodesResultHeight;
|
||||
final double h = layoutNodesResultHeight == 0 ? tlNode.getHeight() : layoutNodesResultHeight + DEFAULT_ROW_HEIGHT;
|
||||
//initial test position
|
||||
double yPos = minY;
|
||||
|
||||
@ -616,8 +601,8 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
localMax = Math.max(yPos2, localMax);
|
||||
|
||||
Timeline tm = new Timeline(new KeyFrame(Duration.seconds(1.0),
|
||||
new KeyValue(tlNode.layoutXProperty(), xPos),
|
||||
new KeyValue(tlNode.layoutYProperty(), yPos)));
|
||||
new KeyValue(tlNode.layoutXProperty(), xPos),
|
||||
new KeyValue(tlNode.layoutYProperty(), yPos)));
|
||||
|
||||
tm.play();
|
||||
// tlNode.relocate(xPos, yPos);
|
||||
@ -625,6 +610,7 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
maxY.set(Math.max(maxY.get(), localMax));
|
||||
return localMax - minY;
|
||||
}
|
||||
private static final int DEFAULT_ROW_HEIGHT = 24;
|
||||
|
||||
private void layoutProjectionMap() {
|
||||
for (final Map.Entry<AggregateEventNode, Line> entry : projectionMap.entrySet()) {
|
||||
@ -642,21 +628,29 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
return getXAxis().localToParent(getXAxis().getDisplayPosition(dt), 0).getX();
|
||||
}
|
||||
|
||||
private static final class DescriptionLODConverter extends StringConverter<Double> {
|
||||
|
||||
@Override
|
||||
public String toString(Double value) {
|
||||
return value == -1 ? "None"
|
||||
: DescriptionLOD.values()[value.intValue()].getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double fromString(String string) {
|
||||
//we never convert from string to double (slider position)
|
||||
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
/**
|
||||
* @return the controller
|
||||
*/
|
||||
public TimeLineController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the filteredEvents
|
||||
*/
|
||||
public FilteredEventsModel getFilteredEvents() {
|
||||
return filteredEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chartContextMenu
|
||||
*/
|
||||
public ContextMenu getChartContextMenu() {
|
||||
return chartContextMenu;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static class StartTimeComparator implements Comparator<Node> {
|
||||
|
||||
@Override
|
||||
@ -669,7 +663,7 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
} else {
|
||||
|
||||
return Long.compare(((AggregateEventNode) n1).getEvent().getSpan().getStartMillis(),
|
||||
(((AggregateEventNode) n2).getEvent().getSpan().getStartMillis()));
|
||||
(((AggregateEventNode) n2).getEvent().getSpan().getStartMillis()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -698,144 +692,13 @@ public class EventDetailChart extends XYChart<DateTime, AggregateEvent> implemen
|
||||
|
||||
}
|
||||
|
||||
/** event handler used for mouse events on {@link AggregateEventNode}s
|
||||
* //TODO: refactor this to put more of the state(slider)in the node */
|
||||
private class EventMouseHandler implements EventHandler<MouseEvent> {
|
||||
|
||||
private final AggregateEventNode aggNode;
|
||||
synchronized void setRequiresLayout(boolean b) {
|
||||
requiresLayout = true;
|
||||
}
|
||||
|
||||
private final AggregateEvent aggEvent;
|
||||
|
||||
private final Slider slider;
|
||||
|
||||
public EventMouseHandler(AggregateEventNode aggNode) {
|
||||
this.aggNode = aggNode;
|
||||
this.aggEvent = aggNode.getEvent();
|
||||
|
||||
//configure slider
|
||||
this.slider = new Slider(-1, 2, -1);
|
||||
slider.setShowTickMarks(true);
|
||||
slider.setShowTickLabels(true);
|
||||
slider.setSnapToTicks(true);
|
||||
slider.setMajorTickUnit(1);
|
||||
slider.setMinorTickCount(0);
|
||||
slider.setBlockIncrement(1);
|
||||
slider.setLabelFormatter(new DescriptionLODConverter());
|
||||
|
||||
//on slider change, reload subnodes
|
||||
InvalidationListener invalidationListener = o -> {
|
||||
if (slider.isValueChanging() == false) {
|
||||
reloadSubNodes();
|
||||
}
|
||||
};
|
||||
slider.valueProperty().addListener(invalidationListener);
|
||||
slider.valueChangingProperty().addListener(invalidationListener);
|
||||
}
|
||||
|
||||
private void reloadSubNodes() {
|
||||
final int value = Math.round(slider.valueProperty().floatValue());
|
||||
aggNode.getSubNodePane().getChildren().clear();
|
||||
aggNode.setEventDetailsVisible(true);
|
||||
if (value == -1) {
|
||||
aggNode.getSubNodePane().getChildren().clear();
|
||||
aggNode.setEventDetailsVisible(true);
|
||||
synchronized (EventDetailChart.this) {
|
||||
requiresLayout = true;
|
||||
}
|
||||
requestChartLayout();
|
||||
} else {
|
||||
final DescriptionLOD newLOD = DescriptionLOD.values()[value];
|
||||
|
||||
final Filter combinedFilter = Filter.intersect(new Filter[]{new TextFilter(aggEvent.getDescription()),
|
||||
new TypeFilter(aggEvent.getType()),
|
||||
filteredEvents.filter().get()});
|
||||
final Interval span = aggEvent.getSpan().withEndMillis(aggEvent.getSpan().getEndMillis() + 1000);
|
||||
LoggedTask<List<AggregateEventNode>> loggedTask = new LoggedTask<List<AggregateEventNode>>("Load sub events", true) {
|
||||
|
||||
@Override
|
||||
protected List<AggregateEventNode> call() throws Exception {
|
||||
|
||||
List<AggregateEvent> aggregatedEvents = filteredEvents.getAggregatedEvents(new ZoomParams(span,
|
||||
filteredEvents.eventTypeZoom().get(),
|
||||
combinedFilter,
|
||||
newLOD));
|
||||
return aggregatedEvents.stream().map((AggregateEvent t) -> {
|
||||
AggregateEventNode subNode = new AggregateEventNode(t, aggNode);
|
||||
subNode.setOnMouseClicked(new EventMouseHandler(subNode));
|
||||
subNode.setLayoutX(getXAxis().getDisplayPosition(new DateTime(t.getSpan().getStartMillis())) - aggNode.getLayoutXCompensation());
|
||||
return subNode;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
try {
|
||||
if (get().size() > 1) {
|
||||
setCursor(Cursor.WAIT);
|
||||
aggNode.setEventDetailsVisible(false);
|
||||
aggNode.getSubNodePane().getChildren().setAll(get());
|
||||
synchronized (EventDetailChart.this) {
|
||||
requiresLayout = true;
|
||||
}
|
||||
requestChartLayout();
|
||||
setCursor(null);
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
Exceptions.printStackTrace(ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
controller.monitorTask(loggedTask);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MouseEvent t) {
|
||||
t.consume();
|
||||
if (t.getButton() == MouseButton.PRIMARY) {
|
||||
if (t.isShiftDown()) {
|
||||
if (selectedNodes.contains(aggNode) == false) {
|
||||
selectedNodes.add(aggNode);
|
||||
}
|
||||
} else if (t.isShortcutDown()) {
|
||||
selectedNodes.removeAll(aggNode);
|
||||
} else if (t.getClickCount() > 1) {
|
||||
slider.increment();
|
||||
} else {
|
||||
selectedNodes.setAll(aggNode);
|
||||
}
|
||||
} else if (t.getButton() == MouseButton.SECONDARY) {
|
||||
|
||||
if (chartContextMenu != null) {
|
||||
chartContextMenu.hide();
|
||||
}
|
||||
//we use a per node menu to remember the slider position
|
||||
ContextMenu nodeContextMenu = aggNode.getContextMenu();
|
||||
if (nodeContextMenu == null) {
|
||||
nodeContextMenu = builContextMenu();
|
||||
aggNode.setContextMenu(nodeContextMenu);
|
||||
}
|
||||
nodeContextMenu.show(aggNode, t.getScreenX(), t.getScreenY());
|
||||
}
|
||||
}
|
||||
|
||||
private ContextMenu builContextMenu() {
|
||||
//should we include a label to remind uer of what group this is for
|
||||
//final MenuItem headingItem = new CustomMenuItem(new Label(aggEvent.getDescription()), false);
|
||||
//headingItem.getStyleClass().remove("menu-item");
|
||||
final Label sliderLabel = new Label("Nested Detail:", slider);
|
||||
sliderLabel.setContentDisplay(ContentDisplay.RIGHT);
|
||||
final MenuItem detailSliderItem = new CustomMenuItem(sliderLabel, false);
|
||||
detailSliderItem.getStyleClass().remove("menu-item");
|
||||
ContextMenu contextMenu = new ContextMenu(detailSliderItem);
|
||||
//we don't reuse item from chartContextMenu because 'place marker' is location specific.
|
||||
//TODO: refactor this so we can reuse chartContextMenu items
|
||||
contextMenu.getItems().addAll(ActionUtils.createContextMenu(
|
||||
Arrays.asList(new ActionGroup("Zoom History", new Back(controller),
|
||||
new Forward(controller)))).getItems());
|
||||
//TODO: add tagging actions here
|
||||
contextMenu.setAutoHide(true);
|
||||
return contextMenu;
|
||||
}
|
||||
@Override
|
||||
protected void requestChartLayout() {
|
||||
super.requestChartLayout(); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,9 @@ package org.sleuthkit.autopsy.timeline.ui.filtering;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
@ -67,6 +70,8 @@ public class FilterSetPanel extends BorderPane implements TimeLineView {
|
||||
|
||||
private TimeLineController controller;
|
||||
|
||||
private final ObservableMap<String, Boolean> expansionMap = FXCollections.observableHashMap();
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
assert applyButton != null : "fx:id=\"applyButton\" was not injected: check your FXML file 'FilterSetPanel.fxml'.";
|
||||
@ -138,6 +143,7 @@ public class FilterSetPanel extends BorderPane implements TimeLineView {
|
||||
|
||||
public FilterSetPanel() {
|
||||
FXMLConstructor.construct(this, "FilterSetPanel.fxml");
|
||||
expansionMap.put("Event Type Filter", Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -152,16 +158,14 @@ public class FilterSetPanel extends BorderPane implements TimeLineView {
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
refresh();
|
||||
|
||||
this.filteredEvents.filter().addListener((Observable o) -> {
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
filterTreeTable.setRoot(new FilterTreeItem(this.filteredEvents.filter().get().copyOf()));
|
||||
filterTreeTable.setRoot(new FilterTreeItem(this.filteredEvents.filter().get().copyOf(), expansionMap));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,23 +175,29 @@ public class FilterSetPanel extends BorderPane implements TimeLineView {
|
||||
private static class FilterCheckBoxCell extends TreeTableCell<AbstractFilter, AbstractFilter> {
|
||||
|
||||
private final CheckBox checkBox = new CheckBox();
|
||||
private SimpleBooleanProperty activeProperty;
|
||||
|
||||
@Override
|
||||
protected void updateItem(AbstractFilter item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
Platform.runLater(() -> {
|
||||
if (activeProperty != null) {
|
||||
checkBox.selectedProperty().unbindBidirectional(activeProperty);
|
||||
}
|
||||
checkBox.disableProperty().unbind();
|
||||
if (item == null) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
checkBox.selectedProperty().unbind();
|
||||
checkBox.disableProperty().unbind();
|
||||
|
||||
} else {
|
||||
setText(item.getDisplayName());
|
||||
checkBox.selectedProperty().bindBidirectional(item.getActiveProperty());
|
||||
activeProperty = item.getActiveProperty();
|
||||
checkBox.selectedProperty().bindBidirectional(activeProperty);
|
||||
checkBox.disableProperty().bind(item.getDisabledProperty());
|
||||
setGraphic(checkBox);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
package org.sleuthkit.autopsy.timeline.ui.filtering;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import org.sleuthkit.autopsy.timeline.filters.CompoundFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
||||
@ -16,15 +19,28 @@ public class FilterTreeItem extends TreeItem<Filter> {
|
||||
* be made for them added added to the children of this
|
||||
* FilterTreeItem
|
||||
*/
|
||||
public FilterTreeItem(Filter f) {
|
||||
public FilterTreeItem(Filter f, ObservableMap<String, Boolean> expansionMap) {
|
||||
super(f);
|
||||
setExpanded(true);
|
||||
|
||||
expansionMap.addListener((MapChangeListener.Change<? extends String, ? extends Boolean> change) -> {
|
||||
if (change.getKey() == f.getDisplayName()) {
|
||||
setExpanded(expansionMap.get(change.getKey()));
|
||||
}
|
||||
});
|
||||
|
||||
if (expansionMap.get(f.getDisplayName()) != null) {
|
||||
setExpanded(expansionMap.get(f.getDisplayName()));
|
||||
}
|
||||
|
||||
expandedProperty().addListener((Observable observable) -> {
|
||||
expansionMap.put(f.getDisplayName(), isExpanded());
|
||||
});
|
||||
|
||||
if (f instanceof CompoundFilter) {
|
||||
CompoundFilter cf = (CompoundFilter) f;
|
||||
|
||||
for (Filter af : cf.getSubFilters()) {
|
||||
getChildren().add(new FilterTreeItem(af));
|
||||
getChildren().add(new FilterTreeItem(af, expansionMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,4 +34,20 @@ public enum DescriptionLOD {
|
||||
private DescriptionLOD(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public DescriptionLOD next() {
|
||||
try {
|
||||
return values()[ordinal() + 1];
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public DescriptionLOD previous() {
|
||||
try {
|
||||
return values()[ordinal() - 1];
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,40 +111,42 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
initializeSlider(timeUnitSlider,
|
||||
() -> {
|
||||
TimeUnits requestedUnit = TimeUnits.values()[new Double(timeUnitSlider.getValue()).intValue()];
|
||||
if (requestedUnit == TimeUnits.FOREVER) {
|
||||
controller.showFullRange();
|
||||
} else {
|
||||
controller.pushTimeRange(IntervalUtils.getIntervalAround(IntervalUtils.middleOf(ZoomSettingsPane.this.filteredEvents.timeRange().get()), requestedUnit.getPeriod()));
|
||||
}
|
||||
},
|
||||
this.filteredEvents.timeRange(),
|
||||
() -> {
|
||||
RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(this.filteredEvents.timeRange().get());
|
||||
ChronoUnit chronoUnit = rangeInfo.getPeriodSize().getChronoUnit();
|
||||
() -> {
|
||||
TimeUnits requestedUnit = TimeUnits.values()[new Double(timeUnitSlider.getValue()).intValue()];
|
||||
if (requestedUnit == TimeUnits.FOREVER) {
|
||||
controller.showFullRange();
|
||||
} else {
|
||||
controller.pushTimeRange(IntervalUtils.getIntervalAround(IntervalUtils.middleOf(ZoomSettingsPane.this.filteredEvents.timeRange().get()), requestedUnit.getPeriod()));
|
||||
}
|
||||
},
|
||||
this.filteredEvents.timeRange(),
|
||||
() -> {
|
||||
RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(this.filteredEvents.timeRange().get());
|
||||
ChronoUnit chronoUnit = rangeInfo.getPeriodSize().getChronoUnit();
|
||||
|
||||
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
|
||||
});
|
||||
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
|
||||
});
|
||||
|
||||
initializeSlider(descrLODSlider,
|
||||
() -> {
|
||||
DescriptionLOD newLOD = DescriptionLOD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
|
||||
controller.pushDescrLOD(newLOD);
|
||||
}, this.filteredEvents.descriptionLOD(),
|
||||
() -> {
|
||||
descrLODSlider.setValue(this.filteredEvents.descriptionLOD().get().ordinal());
|
||||
});
|
||||
() -> {
|
||||
DescriptionLOD newLOD = DescriptionLOD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
|
||||
if (controller.pushDescrLOD(newLOD) == false) {
|
||||
descrLODSlider.setValue(new DescrLODConverter().fromString(filteredEvents.getDescriptionLOD().toString()));
|
||||
}
|
||||
}, this.filteredEvents.descriptionLOD(),
|
||||
() -> {
|
||||
descrLODSlider.setValue(this.filteredEvents.descriptionLOD().get().ordinal());
|
||||
});
|
||||
|
||||
initializeSlider(typeZoomSlider,
|
||||
() -> {
|
||||
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
|
||||
controller.pushEventTypeZoom(newZoomLevel);
|
||||
},
|
||||
this.filteredEvents.eventTypeZoom(),
|
||||
() -> {
|
||||
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoom().get().ordinal());
|
||||
});
|
||||
() -> {
|
||||
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
|
||||
controller.pushEventTypeZoom(newZoomLevel);
|
||||
},
|
||||
this.filteredEvents.eventTypeZoom(),
|
||||
() -> {
|
||||
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoom().get().ordinal());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -178,7 +180,7 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
||||
slider.valueProperty().removeListener(sliderListener);
|
||||
slider.valueChangingProperty().removeListener(sliderListener);
|
||||
|
||||
driverChangHandler.run();
|
||||
Platform.runLater(driverChangHandler);
|
||||
|
||||
slider.valueProperty().addListener(sliderListener);
|
||||
slider.valueChangingProperty().addListener(sliderListener);
|
||||
|
19
README.txt
@ -115,3 +115,22 @@ ImgScalr 4.2 for image resizing in image viewers
|
||||
- Web page: http://www.thebuzzmedia.com/software/imgscalr-java-image-scaling-library/
|
||||
- License: http://www.thebuzzmedia.com/software/imgscalr-java-image-scaling-library/#license
|
||||
|
||||
|
||||
EMBEDED RESOURCES
|
||||
|
||||
This section lists other resources, such as icons, that are used by Autopsy.
|
||||
|
||||
FAMFAMFAM Silk Icons v1.3
|
||||
- Web page: http://www.famfamfam.com/lab/icons/silk/
|
||||
- License: http://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
Fugue Icons v3.5.6
|
||||
- Web page: http://p.yusukekamiyamane.com/
|
||||
- License: http://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
WebHostingHub Glyphs
|
||||
- Web page: http://www.webhostinghub.com/glyphs/
|
||||
- License: http://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
Splashy Icons (free as in free)
|
||||
- Web page: http://splashyfish.com/icons/
|
@ -1,11 +1,11 @@
|
||||
#Updated by build script
|
||||
#Wed, 03 Sep 2014 09:52:11 -0400
|
||||
#Wed, 22 Oct 2014 12:03:43 -0400
|
||||
LBL_splash_window_title=Starting Autopsy
|
||||
SPLASH_HEIGHT=288
|
||||
SPLASH_WIDTH=538
|
||||
SplashProgressBarBounds=3,282,533,6
|
||||
SplashRunningTextBounds=5,266,530,17
|
||||
SPLASH_HEIGHT=560
|
||||
SPLASH_WIDTH=960
|
||||
SplashProgressBarBounds=0,546,960,14
|
||||
SplashRunningTextBounds=0,517,957,25
|
||||
SplashRunningTextColor=0x0
|
||||
SplashRunningTextFontSize=18
|
||||
SplashRunningTextFontSize=27
|
||||
|
||||
currentVersion=Autopsy 3.1.0
|
||||
|
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 785 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 33 KiB |
227
build-windows-installer.xml
Normal file
@ -0,0 +1,227 @@
|
||||
<project name="AutopsyInstallerTargets">
|
||||
|
||||
<!-- ADVANCED INSTALLER TARGETS -->
|
||||
<target name="build-installer-windows" depends="getProps,init-advanced-installer"
|
||||
description="Makes an installer from the opened ZIP file">
|
||||
<antcall target="run-advanced-installer" inheritAll="true" />
|
||||
</target>
|
||||
|
||||
<target name="init-advanced-installer" depends="autoAIPath,inputAIPath"
|
||||
description="Find AdvancedInstaller executable.">
|
||||
<fail unless="ai-exe-path" message="Could not locate Advanced Installer."/>
|
||||
<property environment="env"/>
|
||||
|
||||
<property name="inst-path" value="${nbdist.dir}\${app.name}-installer"/>
|
||||
|
||||
<!-- see what JREs they have installed -->
|
||||
<condition property="jre.home.32">
|
||||
<isset property="env.JRE_HOME_32"/>
|
||||
</condition>
|
||||
<if>
|
||||
<isset property="jre.home.32" />
|
||||
<then>
|
||||
<echo message="32-bit JRE found, 32 bit installer will be built."/>
|
||||
</then>
|
||||
<else>
|
||||
<echo message="32-bit JRE not found. No 32 bit installer will be build. Set the JRE_HOME_32 environment variable to generate a 32-bit installer."/>
|
||||
</else>
|
||||
</if>
|
||||
|
||||
<condition property="jre.home.64">
|
||||
<isset property="env.JRE_HOME_64"/>
|
||||
</condition>
|
||||
<if>
|
||||
<isset property="jre.home.64" />
|
||||
<then>
|
||||
<echo message="64-bit JRE found, 64 bit installer will be built."/>
|
||||
</then>
|
||||
<else>
|
||||
<echo message="64-bit JRE not found. No 64 bit installer will be build. Set the JRE_HOME_64 environment variable to generate a 64-bit installer."/>
|
||||
</else>
|
||||
</if>
|
||||
</target>
|
||||
|
||||
<target name="autoAIPath" description="Attempt to find the AI path based on standard installation location">
|
||||
<property name="AI.path" value="C:\Program Files (x86)\Caphyon\Advanced Installer 10.3\bin\x86\AdvancedInstaller.com" />
|
||||
<available file="${AI.path}"
|
||||
property="ai-exe-path"
|
||||
value="${AI.path}"/>
|
||||
<echo message="${ai-exe-path}" />
|
||||
</target>
|
||||
|
||||
<target name="inputAIPath" unless="ai-exe-path" description="Have the user input the path to the AI executable">
|
||||
<input addProperty="ai-exe-path"
|
||||
message="Enter the location of AdvancedInstaller.com"/>
|
||||
</target>
|
||||
|
||||
<target name="run-advanced-installer" depends="setup-aip-files">
|
||||
<antcall target="build32" inheritAll="true" />
|
||||
<antcall target="build64" inheritAll="true" />
|
||||
</target>
|
||||
|
||||
|
||||
<target name="setup-aip-files" description="Configure the base AIP file and then make 32- and 64-bit versions.">
|
||||
<!-- Copy the template file to add details to -->
|
||||
<property name="aip-path-base" value="${nbdist.dir}\installer_${app.name}_base.aip"/>
|
||||
<copy file="${basedir}/installer_${app.name}/installer_${app.name}.aip" tofile="${aip-path-base}" overwrite="true"/>
|
||||
|
||||
<echo message="${ai-exe-path}" />
|
||||
<echo>Product Version: ${app.version}</echo>
|
||||
|
||||
<!-- generate new product code -->
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path-base} /SetProductCode -langid 1033"/>
|
||||
</exec>
|
||||
|
||||
<!-- Edit the API file to update versions: manual approach allows us to use non-X.Y.Z versions -->
|
||||
|
||||
<replaceregexp file="${aip-path-base}"
|
||||
match="ProductVersion" Value="\d+\.{1}\d+\.{1}\d+"
|
||||
replace="ProductVersion" Value="${app.version}" />
|
||||
|
||||
<property name="aip-path-32" value="${nbdist.dir}\installer_${app.name}_32.aip"/>
|
||||
<copy file="${aip-path-base}" tofile="${aip-path-32}" overwrite="true"/>
|
||||
|
||||
<property name="aip-path-64" value="${nbdist.dir}\installer_${app.name}_64.aip"/>
|
||||
<copy file="${aip-path-base}" tofile="${aip-path-64}" overwrite="true"/>
|
||||
</target>
|
||||
|
||||
|
||||
<target name="copyJRE" description="Copy a given JRE to the installation folder">
|
||||
<var name="new-jre-path" value="${inst-path}\jre"/>
|
||||
<delete failonerror="false" dir="${new-jre-path}"/>
|
||||
<mkdir dir="${new-jre-path}"/>
|
||||
<copy todir="${new-jre-path}" overwrite="true">
|
||||
<fileset dir="${jre-path}">
|
||||
<include name="**/*"/>
|
||||
</fileset>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
|
||||
<target name="build32" if="jre.home.32" description="Builds the 32 bit installer IF JRE_HOME_32 is set.">
|
||||
<property environment="env"/>
|
||||
<var name="aip-path" value="${aip-path-32}"/>
|
||||
|
||||
<var name="aut-bin-name-todelete" value="${app.name}64.exe"/>
|
||||
<var name="aut-bin-name" value="${app.name}.exe"/>
|
||||
|
||||
<var name="jvm.max.mem" value="512" />
|
||||
|
||||
<var name="jre-path" value="${env.JRE_HOME_32}"/>
|
||||
<antcall target="copyJRE" inheritAll="true" />
|
||||
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path-base} /SetAppdir -buildname DefaultBuild -path [ProgramFilesFolder][ProductName]-${app.version}"/>
|
||||
</exec>
|
||||
|
||||
<!-- gstreamer needs special path info to be set -->
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]gstreamer\bin -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]gstreamer\lib\gstreamer-0.10 -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name PATH -value %GSTREAMER_PATH% -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
|
||||
<antcall target="ai-build" inheritAll="true" />
|
||||
|
||||
<delete dir="${nbdist.dir}/installer_${app.name}_32-cache"/>
|
||||
<move file="${nbdist.dir}/installer_${app.name}_32-SetupFiles/installer_${app.name}_32.msi" tofile="${nbdist.dir}/${app.name}-${app.version}-32bit.msi" />
|
||||
</target>
|
||||
|
||||
|
||||
|
||||
<target name="build64" if="jre.home.64" description="Builds the 64 bit installer IF JRE_HOME_64 is set.">
|
||||
<property environment="env"/>
|
||||
<var name="aip-path" value="${aip-path-64}"/>
|
||||
|
||||
<var name="aut-bin-name" value="${app.name}64.exe"/>
|
||||
<var name="aut-bin-name-todelete" value="${app.name}.exe"/>
|
||||
|
||||
<var name="jvm.max.mem" value="2048"/>
|
||||
|
||||
<var name="jre-path" value="${env.JRE_HOME_64}"/>
|
||||
<antcall target="copyJRE" inheritAll="true" />
|
||||
|
||||
|
||||
<echo message="aip-path: ${aip-path}" />
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /SetAppdir -buildname DefaultBuild -path [ProgramFiles64Folder][ProductName]-${app.version}"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /SetPackageType x64"/>
|
||||
</exec>
|
||||
|
||||
<antcall target="ai-build" inheritAll="true" />
|
||||
|
||||
<delete dir="${nbdist.dir}/installer_${app.name}_64-cache"/>
|
||||
<move file="${nbdist.dir}/installer_${app.name}_64-SetupFiles/installer_${app.name}_64.msi" tofile="${nbdist.dir}/${app.name}-${app.version}-64bit.msi" />
|
||||
</target>
|
||||
|
||||
|
||||
<!-- 32/64 specific since config settings are different -->
|
||||
<target name="update-config" description="Updates configuration file with correct JVM args.">
|
||||
<!-- Update configuration file to include jre -->
|
||||
<property name="inst.property.file" value="${inst-path}/etc/${app.name}.conf" />
|
||||
<!-- Sets max heap size to be ${jvm.max.mem} which is set in the run-ai-(32/64) target -->
|
||||
<var name="jvm.args" value=""--branding ${app.name} -J-Xms24m -J-Xmx${jvm.max.mem}m -J-XX:MaxPermSize=128M -J-Xverify:none "" />
|
||||
<propertyfile file="${inst.property.file}">
|
||||
<!-- Note: can be higher on 64 bit systems, should be in sync with project.properties -->
|
||||
<entry key="default_options" value="@JVM_OPTIONS" />
|
||||
<entry key="jdkhome" value=""jre"" />
|
||||
<entry key="default_userdir" value="${HOME}/${APPNAME}" />
|
||||
</propertyfile>
|
||||
<!-- workaround for ant escaping : and = when setting properties -->
|
||||
<replace file="${inst.property.file}" token="@JVM_OPTIONS" value="${jvm.args}" />
|
||||
</target>
|
||||
|
||||
|
||||
<target name="add-ai-files" depends="update-config" description="Add the files in the installation path to the installer file">
|
||||
<foreach target="add-file-or-dir" param="theFile" inheritall="true" inheritrefs="true">
|
||||
<path>
|
||||
<fileset dir="${inst-path}">
|
||||
<include name="*" />
|
||||
</fileset>
|
||||
<dirset dir="${inst-path}">
|
||||
<include name="*"/>
|
||||
</dirset>
|
||||
</path>
|
||||
</foreach>
|
||||
|
||||
<echo message="Removing extra executable..."/>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /DelFile APPDIR\bin\${aut-bin-name-todelete}"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="add-file-or-dir">
|
||||
<condition property="file-or-folder" value="File" else="Folder">
|
||||
<available file="${theFile}" type="file" />
|
||||
</condition>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /Add${file-or-folder} APPDIR ${theFile}" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!-- 32/64 specific since exec changes -->
|
||||
<target name="add-ai-shortcuts" description="Configure installer to have desktop short cuts">
|
||||
<echo message="Adding desktop/menu shortcuts..."/>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line='/edit ${aip-path} /NewShortcut -name "${app.title} ${app.version}" -dir DesktopFolder -target APPDIR\bin\${aut-bin-name} -icon ${inst-path}\icon.ico'/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line='/edit ${aip-path} /NewShortcut -name "${app.title} ${app.version}" -dir SHORTCUTDIR -target APPDIR\bin\${aut-bin-name} -icon ${inst-path}\icon.ico'/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="ai-build" description="Build the installer based on properties set by 32/64 targets.">
|
||||
<antcall target="add-ai-files" inheritAll="true" />
|
||||
<antcall target="add-ai-shortcuts" inheritAll="true" />
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/build ${aip-path}"/>
|
||||
</exec>
|
||||
</target>
|
||||
</project>
|
@ -9,6 +9,7 @@
|
||||
<property name="i686" location="${basedir}/Core/release/modules/lib/i686"/>
|
||||
<property name="crt" location="${basedir}/thirdparty/crt" />
|
||||
|
||||
<import file="build-windows-installer.xml" />
|
||||
|
||||
<target name="makeBaseLibDirs" description="Set up folder hierarchy under release/modules/lib">
|
||||
<mkdir dir="${amd64}"/>
|
||||
@ -138,229 +139,5 @@
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<!-- ADVANCED INSTALLER TARGETS -->
|
||||
<target name="build-installer-windows" depends="getProps,init-advanced-installer"
|
||||
description="Makes an installer from the opened ZIP file">
|
||||
<antcall target="run-advanced-installer" inheritAll="true" />
|
||||
</target>
|
||||
|
||||
<target name="init-advanced-installer" depends="autoAIPath,inputAIPath"
|
||||
description="Find AdvancedInstaller executable.">
|
||||
<fail unless="ai-exe-path" message="Could not locate Advanced Installer."/>
|
||||
<property environment="env"/>
|
||||
|
||||
<property name="inst-path" value="${nbdist.dir}\${app.name}-installer"/>
|
||||
|
||||
<!-- see what JREs they have installed -->
|
||||
<condition property="jre.home.32">
|
||||
<isset property="env.JRE_HOME_32"/>
|
||||
</condition>
|
||||
<if>
|
||||
<isset property="jre.home.32" />
|
||||
<then>
|
||||
<echo message="32-bit JRE found, 32 bit installer will be built."/>
|
||||
</then>
|
||||
<else>
|
||||
<echo message="32-bit JRE not found. No 32 bit installer will be build. Set the JRE_HOME_32 environment variable to generate a 32-bit installer."/>
|
||||
</else>
|
||||
</if>
|
||||
|
||||
<condition property="jre.home.64">
|
||||
<isset property="env.JRE_HOME_64"/>
|
||||
</condition>
|
||||
<if>
|
||||
<isset property="jre.home.64" />
|
||||
<then>
|
||||
<echo message="64-bit JRE found, 64 bit installer will be built."/>
|
||||
</then>
|
||||
<else>
|
||||
<echo message="64-bit JRE not found. No 64 bit installer will be build. Set the JRE_HOME_64 environment variable to generate a 64-bit installer."/>
|
||||
</else>
|
||||
</if>
|
||||
</target>
|
||||
|
||||
<target name="autoAIPath" description="Attempt to find the AI path based on standard installation location">
|
||||
<property name="AI.path" value="C:\Program Files (x86)\Caphyon\Advanced Installer 10.3\bin\x86\AdvancedInstaller.com" />
|
||||
<available file="${AI.path}"
|
||||
property="ai-exe-path"
|
||||
value="${AI.path}"/>
|
||||
<echo message="${ai-exe-path}" />
|
||||
</target>
|
||||
|
||||
<target name="inputAIPath" unless="ai-exe-path" description="Have the user input the path to the AI executable">
|
||||
<input addProperty="ai-exe-path"
|
||||
message="Enter the location of AdvancedInstaller.com"/>
|
||||
</target>
|
||||
|
||||
<target name="run-advanced-installer" depends="setup-aip-files">
|
||||
<antcall target="build32" inheritAll="true" />
|
||||
<antcall target="build64" inheritAll="true" />
|
||||
</target>
|
||||
|
||||
|
||||
<target name="setup-aip-files" description="Configure the base AIP file and then make 32- and 64-bit versions.">
|
||||
<!-- Copy the template file to add details to -->
|
||||
<property name="aip-path-base" value="${nbdist.dir}\installer_${app.name}_base.aip"/>
|
||||
<copy file="${basedir}/installer_${app.name}/installer_${app.name}.aip" tofile="${aip-path-base}" overwrite="true"/>
|
||||
|
||||
<echo message="${ai-exe-path}" />
|
||||
<echo>Product Version: ${app.version}</echo>
|
||||
|
||||
<!-- generate new product code -->
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path-base} /SetProductCode -langid 1033"/>
|
||||
</exec>
|
||||
|
||||
<!-- Edit the API file to update versions: manual approach allows us to use non-X.Y.Z versions -->
|
||||
|
||||
<replaceregexp file="${aip-path-base}"
|
||||
match="ProductVersion" Value="\d+\.{1}\d+\.{1}\d+"
|
||||
replace="ProductVersion" Value="${app.version}" />
|
||||
|
||||
<property name="aip-path-32" value="${nbdist.dir}\installer_${app.name}_32.aip"/>
|
||||
<copy file="${aip-path-base}" tofile="${aip-path-32}" overwrite="true"/>
|
||||
|
||||
<property name="aip-path-64" value="${nbdist.dir}\installer_${app.name}_64.aip"/>
|
||||
<copy file="${aip-path-base}" tofile="${aip-path-64}" overwrite="true"/>
|
||||
</target>
|
||||
|
||||
|
||||
<target name="copyJRE" description="Copy a given JRE to the installation folder">
|
||||
<var name="new-jre-path" value="${inst-path}\jre"/>
|
||||
<delete failonerror="false" dir="${new-jre-path}"/>
|
||||
<mkdir dir="${new-jre-path}"/>
|
||||
<copy todir="${new-jre-path}" overwrite="true">
|
||||
<fileset dir="${jre-path}">
|
||||
<include name="**/*"/>
|
||||
</fileset>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
|
||||
<target name="build32" if="jre.home.32" description="Builds the 32 bit installer IF JRE_HOME_32 is set.">
|
||||
<property environment="env"/>
|
||||
<var name="aip-path" value="${aip-path-32}"/>
|
||||
|
||||
<var name="aut-bin-name-todelete" value="${app.name}64.exe"/>
|
||||
<var name="aut-bin-name" value="${app.name}.exe"/>
|
||||
|
||||
<var name="jvm.max.mem" value="512" />
|
||||
|
||||
<var name="jre-path" value="${env.JRE_HOME_32}"/>
|
||||
<antcall target="copyJRE" inheritAll="true" />
|
||||
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path-base} /SetAppdir -buildname DefaultBuild -path [ProgramFilesFolder][ProductName]-${app.version}"/>
|
||||
</exec>
|
||||
|
||||
<!-- gstreamer needs special path info to be set -->
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]gstreamer\bin -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name GSTREAMER_PATH -value [APPDIR]gstreamer\lib\gstreamer-0.10 -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /NewEnvironment -name PATH -value %GSTREAMER_PATH% -install_operation CreateUpdate -behavior Append -system_variable"/>
|
||||
</exec>
|
||||
|
||||
<antcall target="ai-build" inheritAll="true" />
|
||||
|
||||
<delete dir="${nbdist.dir}/installer_${app.name}_32-cache"/>
|
||||
<move file="${nbdist.dir}/installer_${app.name}_32-SetupFiles/installer_${app.name}_32.msi" tofile="${nbdist.dir}/${app.name}-${app.version}-32bit.msi" />
|
||||
</target>
|
||||
|
||||
|
||||
|
||||
<target name="build64" if="jre.home.64" description="Builds the 64 bit installer IF JRE_HOME_64 is set.">
|
||||
<property environment="env"/>
|
||||
<var name="aip-path" value="${aip-path-64}"/>
|
||||
|
||||
<var name="aut-bin-name" value="${app.name}64.exe"/>
|
||||
<var name="aut-bin-name-todelete" value="${app.name}.exe"/>
|
||||
|
||||
<var name="jvm.max.mem" value="2048"/>
|
||||
|
||||
<var name="jre-path" value="${env.JRE_HOME_64}"/>
|
||||
<antcall target="copyJRE" inheritAll="true" />
|
||||
|
||||
|
||||
<echo message="aip-path: ${aip-path}" />
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /SetAppdir -buildname DefaultBuild -path [ProgramFiles64Folder][ProductName]-${app.version}"/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /SetPackageType x64"/>
|
||||
</exec>
|
||||
|
||||
<antcall target="ai-build" inheritAll="true" />
|
||||
|
||||
<delete dir="${nbdist.dir}/installer_${app.name}_64-cache"/>
|
||||
<move file="${nbdist.dir}/installer_${app.name}_64-SetupFiles/installer_${app.name}_64.msi" tofile="${nbdist.dir}/${app.name}-${app.version}-64bit.msi" />
|
||||
</target>
|
||||
|
||||
|
||||
<!-- 32/64 specific since config settings are different -->
|
||||
<target name="update-config" description="Updates configuration file with correct JVM args.">
|
||||
<!-- Update configuration file to include jre -->
|
||||
<property name="inst.property.file" value="${inst-path}/etc/${app.name}.conf" />
|
||||
<!-- Sets max heap size to be ${jvm.max.mem} which is set in the run-ai-(32/64) target -->
|
||||
<var name="jvm.args" value=""--branding ${app.name} -J-Xms24m -J-Xmx${jvm.max.mem}m -J-XX:MaxPermSize=128M -J-Xverify:none "" />
|
||||
<propertyfile file="${inst.property.file}">
|
||||
<!-- Note: can be higher on 64 bit systems, should be in sync with project.properties -->
|
||||
<entry key="default_options" value="@JVM_OPTIONS" />
|
||||
<entry key="jdkhome" value=""jre"" />
|
||||
<entry key="default_userdir" value="${HOME}/${APPNAME}" />
|
||||
</propertyfile>
|
||||
<!-- workaround for ant escaping : and = when setting properties -->
|
||||
<replace file="${inst.property.file}" token="@JVM_OPTIONS" value="${jvm.args}" />
|
||||
</target>
|
||||
|
||||
|
||||
<target name="add-ai-files" depends="update-config" description="Add the files in the installation path to the installer file">
|
||||
<foreach target="add-file-or-dir" param="theFile" inheritall="true" inheritrefs="true">
|
||||
<path>
|
||||
<fileset dir="${inst-path}">
|
||||
<include name="*" />
|
||||
</fileset>
|
||||
<dirset dir="${inst-path}">
|
||||
<include name="*"/>
|
||||
</dirset>
|
||||
</path>
|
||||
</foreach>
|
||||
|
||||
<echo message="Removing extra executable..."/>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /DelFile APPDIR\bin\${aut-bin-name-todelete}"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="add-file-or-dir">
|
||||
<condition property="file-or-folder" value="File" else="Folder">
|
||||
<available file="${theFile}" type="file" />
|
||||
</condition>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/edit ${aip-path} /Add${file-or-folder} APPDIR ${theFile}" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!-- 32/64 specific since exec changes -->
|
||||
<target name="add-ai-shortcuts" description="Configure installer to have desktop short cuts">
|
||||
<echo message="Adding desktop/menu shortcuts..."/>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line='/edit ${aip-path} /NewShortcut -name "${app.title} ${app.version}" -dir DesktopFolder -target APPDIR\bin\${aut-bin-name} -icon ${inst-path}\icon.ico'/>
|
||||
</exec>
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line='/edit ${aip-path} /NewShortcut -name "${app.title} ${app.version}" -dir SHORTCUTDIR -target APPDIR\bin\${aut-bin-name} -icon ${inst-path}\icon.ico'/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="ai-build" description="Build the installer based on properties set by 32/64 targets.">
|
||||
<antcall target="add-ai-files" inheritAll="true" />
|
||||
<antcall target="add-ai-shortcuts" inheritAll="true" />
|
||||
<exec executable="${ai-exe-path}">
|
||||
<arg line="/build ${aip-path}"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
</project>
|
||||
|
28
build.xml
@ -12,6 +12,7 @@
|
||||
<property name="netbeans-plat-version" value="7.3" />
|
||||
<property name="nbplatform.active.dir" value="${basedir}/netbeans-plat/${netbeans-plat-version}" />
|
||||
|
||||
<!-- Determine platform and include specific file -->
|
||||
<condition property="os.family" value="unix">
|
||||
<os family="unix"/>
|
||||
</condition>
|
||||
@ -19,10 +20,10 @@
|
||||
<os family="windows"/>
|
||||
</condition>
|
||||
<import file="build-${os.family}.xml"/>
|
||||
<property name="test-input" location="Test/input"/>
|
||||
<property name="thirdparty.dir" value="${basedir}/thirdparty" />
|
||||
|
||||
|
||||
<!-- Third party tools dependencies -->
|
||||
<!-- import ant-contrib tools -->
|
||||
<property name="thirdparty.dir" value="${basedir}/thirdparty" />
|
||||
<property name="ant-contrib.dir" value="${thirdparty.dir}/ant-contrib/1.0b3" />
|
||||
<property name="ant.contrib.jar" value="${ant-contrib.dir}/ant-contrib.jar" />
|
||||
<taskdef resource="net/sf/antcontrib/antlib.xml">
|
||||
@ -31,12 +32,11 @@
|
||||
</classpath>
|
||||
</taskdef>
|
||||
|
||||
<target name="getJunit">
|
||||
<property name="junit.dir" value="${thirdparty.dir}/junit/${netbeans-plat-version}" />
|
||||
<unzip src="${junit.dir}/junit.zip" dest="${nbplatform.active.dir}"/>
|
||||
</target>
|
||||
|
||||
<!-- This seems really bad to be hard coded, but I couldn't find a better solution -->
|
||||
<path id="jni-path">
|
||||
<pathelement location="${basedir}/build/cluster/modules/org-sleuthkit-datamodel.jar"/>
|
||||
<path refid="cluster.path.id" />
|
||||
</path>
|
||||
|
||||
<!-- Verify that the TSK_HOME env variable is set -->
|
||||
<target name="findTSK">
|
||||
@ -48,10 +48,8 @@
|
||||
<echo> TSK_HOME: ${env.TSK_HOME}</echo>
|
||||
</target>
|
||||
|
||||
<target name="getJunit">
|
||||
<property name="junit.dir" value="${thirdparty.dir}/junit/${netbeans-plat-version}" />
|
||||
<unzip src="${junit.dir}/junit.zip" dest="${nbplatform.active.dir}"/>
|
||||
</target>
|
||||
|
||||
|
||||
|
||||
<!-- This target will create a custom ZIP file for us. It first uses the general
|
||||
ZIP target and then opens it up and adds in any files that we want. This is where we customize the
|
||||
@ -185,6 +183,11 @@
|
||||
|
||||
</target>
|
||||
|
||||
<!-- This seems really bad to be hard coded, but I couldn't find a better solution -->
|
||||
<path id="jni-path">
|
||||
<pathelement location="${basedir}/build/cluster/modules/org-sleuthkit-datamodel.jar"/>
|
||||
<path refid="cluster.path.id" />
|
||||
</path>
|
||||
<target name="jni" depends="build,findTSK">
|
||||
<javah verbose="yes" outputFile="${env.TSK_HOME}/bindings/java/tsk_jni/tsk_jni/dataModel_SleuthkitJNI.h">
|
||||
<class name="org.sleuthkit.datamodel.SleuthkitJNI" />
|
||||
@ -229,6 +232,7 @@
|
||||
<antcall target="build-installer-${os.family}" />
|
||||
</target>
|
||||
|
||||
<property name="test-input" location="Test/input"/>
|
||||
<target name="test-download-imgs" description="Get test images and store them in the path represented by the test-input variable.">
|
||||
<available file="${test-input}/nps-2008-jean.E01" property="img-present-1"/>
|
||||
<available file="${test-input}/nps-2008-jean.E02" property="img-present-2"/>
|
||||
|
@ -50,6 +50,8 @@ from neededLib.mylib import neededClass
|
||||
|
||||
Jython will look in the module's folder to resolve these libraries.
|
||||
|
||||
NOTE: Your code or third party modules may depend on core python modules that are not distributed with Jython. You will need to copy those libraries into your folder as though they were external libraries.
|
||||
|
||||
\section mod_dev_py_distribute Distribution
|
||||
To distribute and share your Python module, ZIP up the folder and send it around. Other users of the module should expand the ZIP file and drop the folder into their Autopsy Python folder.
|
||||
|
||||
|
BIN
icons/icon.icns
BIN
icons/icon.ico
Before Width: | Height: | Size: 284 KiB After Width: | Height: | Size: 361 KiB |