Interim commit

- Its working with 3 panels, the alignment isn't great with 3 panels though.
This commit is contained in:
Raman Arora 2020-06-19 11:07:40 -04:00
parent 7c4340fd3c
commit d460abe1a8
6 changed files with 677 additions and 185 deletions

View File

@ -182,7 +182,7 @@ public final class CommunicationArtifactViewerHelper {
* @param gridbagLayout Layout to use. * @param gridbagLayout Layout to use.
* @param constraints Constrains to use. * @param constraints Constrains to use.
*/ */
private static void addBlankLine(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) { static void addBlankLine(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) {
constraints.gridy++; constraints.gridy++;
constraints.gridx = 0; constraints.gridx = 0;
@ -194,25 +194,44 @@ public final class CommunicationArtifactViewerHelper {
} }
/** /**
* Adds a label/key to the panel. * Adds a label/key to the panel at col 0.
* *
* @param panel Panel to update. * @param panel Panel to update.
* @param gridbagLayout Layout to use. * @param gridbagLayout Layout to use.
* @param constraints Constrains to use. * @param constraints Constrains to use.
* @param keyString Key name to display. * @param keyString Key name to display.
*
* @return Label added.
*/ */
static void addKey(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String keyString) { static JLabel addKey(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String keyString) {
return addKeyAtCol(panel, gridbagLayout, constraints, keyString, 0 );
}
/**
* Adds a label/key to the panel at specified column.
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
* @param constraints Constrains to use.
* @param keyString Key name to display.
* @param gridx column index, must be less than MAX_COLS - 1.
*
* @return Label added.
*/
static JLabel addKeyAtCol(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String keyString, int gridx ) {
// create label
javax.swing.JLabel keyLabel = new javax.swing.JLabel();
constraints.gridy++; constraints.gridy++;
constraints.gridx = 0; constraints.gridx = gridx < MAX_COLS - 1? gridx : MAX_COLS - 2;
Insets savedInsets = constraints.insets; Insets savedInsets = constraints.insets;
// Set inset to indent in // Set inset to indent in
constraints.insets = new java.awt.Insets(0, LEFT_INDENT, 0, 0); constraints.insets = new java.awt.Insets(0, LEFT_INDENT, 0, 0);
// create label, // set text
javax.swing.JLabel keyLabel = new javax.swing.JLabel();
keyLabel.setText(keyString + ": "); keyLabel.setText(keyString + ": ");
// add to panel // add to panel
@ -221,27 +240,47 @@ public final class CommunicationArtifactViewerHelper {
// restore inset // restore inset
constraints.insets = savedInsets; constraints.insets = savedInsets;
return keyLabel;
} }
/** /**
* Adds a value string to the panel. * Adds a value string to the panel at col 1.
* *
* @param panel Panel to update. * @param panel Panel to update.
* @param gridbagLayout Layout to use. * @param gridbagLayout Layout to use.
* @param constraints Constrains to use. * @param constraints Constrains to use.
* @param keyString Value string to display. * @param keyString Value string to display.
*
* @return Label added.
*/ */
static void addValue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String valueString) { static JLabel addValue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String valueString) {
return addValueAtCol(panel, gridbagLayout, constraints, valueString, 1);
}
constraints.gridx = 1; /**
* Adds a value string to the panel at col 1.
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
* @param constraints Constrains to use.
* @param keyString Value string to display.
* @param gridx Column index, must be less than MAX_COLS;
*
* @return Label added.
*/
static JLabel addValueAtCol(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String valueString, int gridx) {
// create label,
javax.swing.JLabel valueField = new javax.swing.JLabel();
constraints.gridx = gridx < MAX_COLS ? gridx : MAX_COLS - 1 ;
int savedGridwidth = constraints.gridwidth; int savedGridwidth = constraints.gridwidth;
// let the value span 2 cols // let the value span 2 cols
constraints.gridwidth = 2; constraints.gridwidth = 2;
// create label, // set text
javax.swing.JLabel valueField = new javax.swing.JLabel();
valueField.setText(valueString); valueField.setText(valueString);
// attach a right click menu with Copy option // attach a right click menu with Copy option
@ -261,6 +300,8 @@ public final class CommunicationArtifactViewerHelper {
// end the line // end the line
addLineEndGlue(panel, gridbagLayout, constraints); addLineEndGlue(panel, gridbagLayout, constraints);
return valueField;
} }
/** /**

View File

@ -541,11 +541,11 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
} }
@NbBundle.Messages({ @NbBundle.Messages({
"ContactArtifactViewer_persona_label=Persona ", //"ContactArtifactViewer_persona_label=Persona ",
"ContactArtifactViewer_persona_text_none=None found", //"ContactArtifactViewer_persona_text_none=None found",
"ContactArtifactViewer_persona_button_view=View", //"ContactArtifactViewer_persona_button_view=View",
"ContactArtifactViewer_persona_button_new=Create", //"ContactArtifactViewer_persona_button_new=Create",
"ContactArtifactViewer_missing_account_label=Missing Account: " //"ContactArtifactViewer_missing_account_label=Missing Account: "
}) })
/** /**
@ -827,7 +827,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
} }
@NbBundle.Messages({ @NbBundle.Messages({
"ContactArtifactViewer_persona_account_justification=Account found in Contact artifact" // "ContactArtifactViewer_persona_account_justification=Account found in Contact artifact"
}) })
@Override @Override

View File

@ -18,4 +18,48 @@
</AuxValues> </AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="contactDetailsPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
</Container>
<Container class="javax.swing.JPanel" name="personasPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
</Container>
<Container class="javax.swing.JPanel" name="sourcePanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container>
<Component class="javax.swing.Box$Filler" name="filler1">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 32767]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.VerticalGlue"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Form> </Form>

View File

@ -1,13 +1,28 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * Autopsy Forensic Browser
* To change this template file, choose Tools | Templates *
* and open the template in the editor. * Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ */
package org.sleuthkit.autopsy.contentviewers; package org.sleuthkit.autopsy.contentviewers;
import java.awt.Component; import java.awt.Component;
import java.awt.GridBagConstraints; import java.awt.GridBagConstraints;
import java.awt.GridBagLayout; import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -23,8 +38,8 @@ import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@ -35,6 +50,10 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona; import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount; import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount;
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialog;
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialogCallback;
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsMode;
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
@ -43,8 +62,7 @@ import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
* * This class displays the TSK_CONTACT artifact.
* @author raman
*/ */
@ServiceProvider(service = ArtifactContentViewer.class) @ServiceProvider(service = ArtifactContentViewer.class)
public class ContactArtifactViewerNew extends javax.swing.JPanel implements ArtifactContentViewer { public class ContactArtifactViewerNew extends javax.swing.JPanel implements ArtifactContentViewer {
@ -52,13 +70,19 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
private final static Logger logger = Logger.getLogger(ContactArtifactViewer.class.getName()); private final static Logger logger = Logger.getLogger(ContactArtifactViewer.class.getName());
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private GridBagLayout m_gridBagLayout = new GridBagLayout(); private final static int LEFT_INSET = 12;
private GridBagConstraints m_constraints = new GridBagConstraints();
// contact name, if available. //private GridBagLayout m_gridBagLayout = new GridBagLayout();
private String contactName; // TBD: is this really needed as class member? //private GridBagConstraints m_constraints = new GridBagConstraints();
private BlackboardArtifact contactArtifact;
private String contactName;
private String datasourceName;
//private javax.swing.JLabel contactImage; // TBD: is this really needed as class member? private List<BlackboardAttribute> phoneNumList = new ArrayList<>();
private List<BlackboardAttribute> emailList = new ArrayList<>();
private List<BlackboardAttribute> nameList = new ArrayList<>();
private List<BlackboardAttribute> otherList = new ArrayList<>();
private List<BlackboardAttribute> accountAttributesList = new ArrayList<>();
private final static String DEFAULT_IMAGE_PATH = "/org/sleuthkit/autopsy/images/defaultContact.png"; private final static String DEFAULT_IMAGE_PATH = "/org/sleuthkit/autopsy/images/defaultContact.png";
private final ImageIcon defaultImage; private final ImageIcon defaultImage;
@ -70,10 +94,10 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
// account identifier attributes of the Contact artifact. // account identifier attributes of the Contact artifact.
private final Map<Persona, ArrayList<CentralRepoAccount>> contactUniquePersonasMap = new HashMap<>(); private final Map<Persona, ArrayList<CentralRepoAccount>> contactUniquePersonasMap = new HashMap<>();
privbate ContactPersonaSearcherTask personaSearchTask; private ContactPersonaSearcherTask personaSearchTask;
/** /**
* Creates new form ContactArtifactViewerNew * Creates new form ContactArtifactViewer
*/ */
public ContactArtifactViewerNew() { public ContactArtifactViewerNew() {
initComponents(); initComponents();
@ -89,9 +113,45 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
contactDetailsPanel = new javax.swing.JPanel();
personasPanel = new javax.swing.JPanel();
sourcePanel = new javax.swing.JPanel();
filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767));
setToolTipText(""); // NOI18N setToolTipText(""); // NOI18N
setLayout(new java.awt.GridBagLayout()); setLayout(new java.awt.GridBagLayout());
contactDetailsPanel.setLayout(new java.awt.GridBagLayout());
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
add(contactDetailsPanel, gridBagConstraints);
personasPanel.setLayout(new java.awt.GridBagLayout());
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
add(personasPanel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
add(sourcePanel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 4;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
add(filler1, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
@Override @Override
@ -103,16 +163,48 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
return; return;
} }
List<BlackboardAttribute> phoneNumList = new ArrayList<>();
List<BlackboardAttribute> emailList = new ArrayList<>();
List<BlackboardAttribute> nameList = new ArrayList<>();
List<BlackboardAttribute> otherList = new ArrayList<>();
List<BlackboardAttribute> accountAttributesList = new ArrayList<>();
String datasourceName;
try { try {
extractArtifactData(artifact);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error getting attributes for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex);
return;
}
updateView();
this.revalidate();
this.repaint();
}
@Override
public Component getComponent() {
// Slap a vertical scrollbar on the panel.
return new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
}
@Override
public boolean isSupported(BlackboardArtifact artifact) {
return artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID();
}
/**
* Extracts data from the artifact to be displayed in the panel.
*
* @param artifact Artifact to show.
* @throws TskCoreException
*/
private void extractArtifactData(BlackboardArtifact artifact) throws TskCoreException {
this.contactArtifact = artifact;
phoneNumList = new ArrayList<>();
emailList = new ArrayList<>();
nameList = new ArrayList<>();
otherList = new ArrayList<>();
accountAttributesList = new ArrayList<>();
// Get all the attributes and group them by the section panels they go in // Get all the attributes and group them by the section panels they go in
for (BlackboardAttribute bba : artifact.getAttributes()) { for (BlackboardAttribute bba : contactArtifact.getAttributes()) {
if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) {
phoneNumList.add(bba); phoneNumList.add(bba);
accountAttributesList.add(bba); accountAttributesList.add(bba);
@ -129,89 +221,106 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
} }
} }
datasourceName = artifact.getDataSource().getName(); datasourceName = contactArtifact.getDataSource().getName();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error getting attributes for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex);
return;
} }
/**
* Updates the view with the data extracted from the artifact.
*/
private void updateView() {
updateContactImage(artifact); // Update contact name, image, phone numbers
updateContactDetails();
// show a empty Personas panel and kick off a serch for personas
initiatePersonasSearch();
// update artifact source panel
updateSource();
}
/**
* Updates the view with contact's details.
*/
@NbBundle.Messages({
"ContactArtifactViewer_phones_header=Phone",
"ContactArtifactViewer_eamils_header=Email",
"ContactArtifactViewer_others_header=Other",})
private void updateContactDetails() {
GridBagLayout contactPanelLayout = new GridBagLayout();
GridBagConstraints contactPanelConstraints = new GridBagConstraints();
contactPanelConstraints.anchor = GridBagConstraints.FIRST_LINE_START;
contactPanelConstraints.gridy = 0;
contactPanelConstraints.gridx = 0;
contactPanelConstraints.weighty = 0.05;
contactPanelConstraints.weightx = 0.05;
contactPanelConstraints.insets = new java.awt.Insets(0, 0, 0, 0);
contactPanelConstraints.fill = GridBagConstraints.NONE;
updateContactImage(contactPanelLayout, contactPanelConstraints);
// update name section // update name section
updateContactName(nameList); updateContactName(contactPanelLayout, contactPanelConstraints);
// update contact attributes sections // update contact attributes sections
updateSection(phoneNumList, "Phones"); updateContactMethodSection(phoneNumList, Bundle.ContactArtifactViewer_phones_header(), contactPanelLayout, contactPanelConstraints);
updateSection(emailList, "Emails"); updateContactMethodSection(emailList, Bundle.ContactArtifactViewer_eamils_header(), contactPanelLayout, contactPanelConstraints);
updateSection(otherList, "Others"); updateContactMethodSection(otherList, Bundle.ContactArtifactViewer_others_header(), contactPanelLayout, contactPanelConstraints);
updateSource(datasourceName); CommunicationArtifactViewerHelper.addBlankLine(this.contactDetailsPanel, contactPanelLayout, contactPanelConstraints);
CommunicationArtifactViewerHelper.addPageEndGlue(this, m_gridBagLayout, this.m_constraints); contactDetailsPanel.setLayout(contactPanelLayout);
contactDetailsPanel.revalidate();
// repaint contactDetailsPanel.repaint();
this.setLayout(m_gridBagLayout);
this.revalidate();
this.repaint();
} }
@Override
public Component getComponent() {
// Slap a vertical scrollbar on the panel.
return new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
}
@Override
public boolean isSupported(BlackboardArtifact artifact) {
return artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID();
}
/** /**
* Updates the contact image in the view. * Updates the contact image in the view.
* *
* @param artifact * @param artifact
*/ */
@NbBundle.Messages({ @NbBundle.Messages({
"ContactArtifactViewer.contactImage.text=", "ContactArtifactViewer.contactImage.text=",})
}) private void updateContactImage(GridBagLayout contactPanelLayout, GridBagConstraints contactPanelConstraints) {
private void updateContactImage(BlackboardArtifact artifact) {
contactPanelConstraints.gridy = 0;
contactPanelConstraints.gridx = 0;
javax.swing.JLabel contactImage = new javax.swing.JLabel(); javax.swing.JLabel contactImage = new javax.swing.JLabel();
contactImage.setIcon(getImageFromArtifact(artifact)); contactImage.setIcon(getImageFromArtifact(contactArtifact));
contactImage.setText(Bundle.ContactArtifactViewer_contactImage_text()); contactImage.setText(Bundle.ContactArtifactViewer_contactImage_text());
// add image to top left corner of the page. // add image to top left corner of the page.
CommunicationArtifactViewerHelper.addComponent(this, m_gridBagLayout, this.m_constraints, contactImage); CommunicationArtifactViewerHelper.addComponent(this.contactDetailsPanel, contactPanelLayout, contactPanelConstraints, contactImage);
m_constraints.gridy++; contactPanelConstraints.gridy++;
} }
/** /**
* Updates the contact name in the view from the list of attributes. * Updates the contact name in the view from the list of attributes.
* *
* @param attributesList List of attributes that might have the contact name. * @param attributesList List of attributes that might have the contact
* name.
*/ */
@NbBundle.Messages({ @NbBundle.Messages({
"ContactArtifactViewer_contactname_unknown=Unknown", "ContactArtifactViewer_contactname_unknown=Unknown",})
}) private void updateContactName(GridBagLayout contactPanelLayout, GridBagConstraints contactPanelConstraints) {
private void updateContactName(List<BlackboardAttribute> nameAttributesList) {
boolean foundName = false; boolean foundName = false;
for (BlackboardAttribute bba : nameAttributesList) { for (BlackboardAttribute bba : this.nameList) {
if (StringUtils.isEmpty(bba.getValueString()) == false) { if (StringUtils.isEmpty(bba.getValueString()) == false) {
contactName = bba.getDisplayString(); contactName = bba.getDisplayString();
// TBD: need to increase the font size of the page header by 2 CommunicationArtifactViewerHelper.addHeader(this.contactDetailsPanel, contactPanelLayout, contactPanelConstraints, contactName);
CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, contactName);
foundName = true; foundName = true;
break; break;
} }
} }
if (foundName == false) { if (foundName == false) {
CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, Bundle.ContactArtifactViewer_contactname_unknown()); CommunicationArtifactViewerHelper.addHeader(this.contactDetailsPanel, contactPanelLayout, contactPanelConstraints, Bundle.ContactArtifactViewer_contactname_unknown());
} }
} }
@ -222,27 +331,52 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
* @param sectionAttributesList list of attributes to display. * @param sectionAttributesList list of attributes to display.
* @param sectionLabel section name label. * @param sectionLabel section name label.
*/ */
private void updateSection(List<BlackboardAttribute> sectionAttributesList, String sectionHeader) { @NbBundle.Messages({
"ContactArtifactViewer_plural_suffix=s",})
private void updateContactMethodSection(List<BlackboardAttribute> sectionAttributesList, String sectionHeader, GridBagLayout contactPanelLayout, GridBagConstraints contactPanelConstraints) {
// If there are no attributes for this section, do nothing // If there are no attributes for this section, do nothing
if (sectionAttributesList.isEmpty()) { if (sectionAttributesList.isEmpty()) {
return; return;
} }
CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, sectionHeader); String sectionHeaderString = sectionHeader;
if (sectionAttributesList.size() > 1) {
sectionHeaderString = sectionHeaderString.concat(Bundle.ContactArtifactViewer_plural_suffix());
}
CommunicationArtifactViewerHelper.addHeader(this.contactDetailsPanel, contactPanelLayout, contactPanelConstraints, sectionHeaderString);
for (BlackboardAttribute bba : sectionAttributesList) { for (BlackboardAttribute bba : sectionAttributesList) {
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, m_constraints, bba.getAttributeType().getDisplayName()); CommunicationArtifactViewerHelper.addKey(this.contactDetailsPanel, contactPanelLayout, contactPanelConstraints, bba.getAttributeType().getDisplayName());
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, m_constraints, bba.getDisplayString()); CommunicationArtifactViewerHelper.addValue(this.contactDetailsPanel, contactPanelLayout, contactPanelConstraints, bba.getDisplayString());
} }
} }
/**
* Updates the source section.
*/
@NbBundle.Messages({ @NbBundle.Messages({
"ContactArtifactViewer_heading_Source=Source", "ContactArtifactViewer_heading_Source=Source",
"ContactArtifactViewer_label_datasource=Data Source",}) "ContactArtifactViewer_label_datasource=Data Source",})
private void updateSource(String datasourceName) { private void updateSource() {
CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, Bundle.ContactArtifactViewer_heading_Source());
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, Bundle.ContactArtifactViewer_label_datasource()); GridBagLayout sourcePanelLayout = new GridBagLayout();
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, datasourceName); GridBagConstraints sourcePanelConstraints = new GridBagConstraints();
sourcePanelConstraints.anchor = GridBagConstraints.FIRST_LINE_START;
sourcePanelConstraints.gridy = 0;
sourcePanelConstraints.gridx = 0;
sourcePanelConstraints.weighty = 0.05;
sourcePanelConstraints.weightx = 0.05;
sourcePanelConstraints.insets = new java.awt.Insets(0, 0, 0, 0);
sourcePanelConstraints.fill = GridBagConstraints.NONE;
CommunicationArtifactViewerHelper.addHeader(this.sourcePanel, sourcePanelLayout, sourcePanelConstraints, Bundle.ContactArtifactViewer_heading_Source());
CommunicationArtifactViewerHelper.addKey(this.sourcePanel, sourcePanelLayout, sourcePanelConstraints, Bundle.ContactArtifactViewer_label_datasource());
CommunicationArtifactViewerHelper.addValue(this.sourcePanel, sourcePanelLayout, sourcePanelConstraints, datasourceName);
sourcePanel.setLayout(sourcePanelLayout);
sourcePanel.revalidate();
sourcePanel.repaint();
} }
/** /**
@ -253,59 +387,77 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
* @throws CentralRepoException * @throws CentralRepoException
*/ */
@NbBundle.Messages({ @NbBundle.Messages({
"ContactArtifactViewer_persona_searching= Persona", "ContactArtifactViewer_persona_header=Persona", //"ContactArtifactViewer_persona_searching = Searching...",
"ContactArtifactViewer_persona_searching= Searching...", //"ContactArtifactViewer_persona_unknown=Unknown"
"ContactArtifactViewer_persona_unknown=Unknown"
}) })
private void initiatePersonasSearch(List<BlackboardAttribute> accountAttributesList) throws CentralRepoException {
JLabel personaHeader = CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, Bundle.ContactArtifactViewer_persona_searching()); private void initiatePersonasSearch() {
//CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, Bundle.ContactArtifactViewer_label_datasource()); GridBagLayout personasPanelLayout = new GridBagLayout();
GridBagConstraints personasPanelConstraints = new GridBagConstraints();
personasPanelConstraints.anchor = GridBagConstraints.FIRST_LINE_START;
personasPanelConstraints.gridy = 0;
personasPanelConstraints.gridx = 0;
personasPanelConstraints.weighty = 0.05;
personasPanelConstraints.weightx = 0.05;
personasPanelConstraints.insets = new java.awt.Insets(0, 0, 0, 0);
personasPanelConstraints.fill = GridBagConstraints.NONE;
personasLabel.setVisible(true); // add a section header
JLabel personaHeader = CommunicationArtifactViewerHelper.addHeader(this.personasPanel, personasPanelLayout, personasPanelConstraints, Bundle.ContactArtifactViewer_persona_header());
personasPanelConstraints.gridy++;
// add a status label
String personaStatusLabelText = CentralRepository.isEnabled() String personaStatusLabelText = CentralRepository.isEnabled()
? Bundle.ContactArtifactViewer_persona_searching() ? Bundle.ContactArtifactViewer_persona_searching()
: Bundle.ContactArtifactViewer_persona_unknown(); : Bundle.ContactArtifactViewer_persona_unknown();
javax.swing.JLabel statusLabel = new javax.swing.JLabel();
statusLabel.setText(personaStatusLabelText);
CommunicationArtifactViewerHelper.addComponent(this.personasPanel, personasPanelLayout, personasPanelConstraints, statusLabel);
// End of panel, add a blank line
CommunicationArtifactViewerHelper.addBlankLine(this.personasPanel, personasPanelLayout, personasPanelConstraints);
if (CentralRepository.isEnabled()) { if (CentralRepository.isEnabled()) {
// Kick off a background task to serach for personas for the contact // Kick off a background task to serach for personas for the contact
ContactPersonaSearcherTask personaSearchTask = new ContactPersonaSearcherTask(accountAttributesList); personaSearchTask = new ContactPersonaSearcherTask(accountAttributesList);
personaSearchTask.execute(); personaSearchTask.execute();
} else { } else {
personaHeader.setEnabled(false); personaHeader.setEnabled(false);
statusLabel.setEnabled(false);
// RAMAN TBD: add a Unknown diabled label at gridx=1;
} }
personasPanel.setLayout(personasPanelLayout);
personasPanel.revalidate();
personasPanel.repaint();
} }
/** /**
* Updates the Persona panel with the gathered persona information. * Updates the Persona panel with the gathered persona information.
*/ */
private void updatePersonasPanel() { private void updatePersonas() {
// Clear out the panel // Clear out the panel
personasPanel.removeAll(); personasPanel.removeAll();
GridBagLayout gridBagLayout = new GridBagLayout(); GridBagLayout personasPanelLayout = new GridBagLayout();
GridBagConstraints constraints = new GridBagConstraints(); GridBagConstraints personasPanelConstraints = new GridBagConstraints();
constraints.anchor = GridBagConstraints.FIRST_LINE_START; personasPanelConstraints.anchor = GridBagConstraints.FIRST_LINE_START;
constraints.gridx = 0; personasPanelConstraints.gridx = 0;
constraints.gridy = 0; personasPanelConstraints.gridy = 0;
constraints.insets = new java.awt.Insets(TOP_INSET, LEFT_INSET, 0, 0); personasPanelConstraints.insets = new java.awt.Insets(0, 0, 0, 0);
// add a section header
CommunicationArtifactViewerHelper.addHeader(this.personasPanel, personasPanelLayout, personasPanelConstraints, Bundle.ContactArtifactViewer_persona_header());
personasPanelConstraints.gridy++;
if (contactUniquePersonasMap.isEmpty()) { if (contactUniquePersonasMap.isEmpty()) {
showPersona(null, Collections.emptyList(), gridBagLayout, constraints); showPersona(null, Collections.emptyList(), personasPanelLayout, personasPanelConstraints);
} else { } else {
for (Map.Entry<Persona, ArrayList<CentralRepoAccount>> entry : contactUniquePersonasMap.entrySet()) { for (Map.Entry<Persona, ArrayList<CentralRepoAccount>> entry : contactUniquePersonasMap.entrySet()) {
List<CentralRepoAccount> missingAccounts = new ArrayList<>(); List<CentralRepoAccount> missingAccounts = new ArrayList<>();
@ -318,47 +470,142 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
} }
} }
showPersona(entry.getKey(), missingAccounts, gridBagLayout, constraints); showPersona(entry.getKey(), missingAccounts, personasPanelLayout, personasPanelConstraints);
personasPanelConstraints.gridy += 2;
constraints.gridy += 2;
} }
} }
personasPanel.setLayout(gridBagLayout); CommunicationArtifactViewerHelper.addBlankLine(this.personasPanel, personasPanelLayout, personasPanelConstraints);
personasPanel.setLayout(personasPanelLayout);
personasPanel.setSize(personasPanel.getPreferredSize()); personasPanel.setSize(personasPanel.getPreferredSize());
personasPanel.revalidate(); personasPanel.revalidate();
personasPanel.repaint(); personasPanel.repaint();
} }
/**
* Displays the given persona in the persona panel.
*
* @param persona Persona to display.
* @param missingAccountsList List of contact accounts this persona may be
* missing.
* @param gridBagLayout Layout to use.
* @param constraints layout constraints.
*
* @throws CentralRepoException
*/
@NbBundle.Messages({
"ContactArtifactViewer_persona_label=Persona ",
"ContactArtifactViewer_persona_name_unknown=Unknown",
"ContactArtifactViewer_persona_button_view=View",
"ContactArtifactViewer_persona_button_new=Create",
"ContactArtifactViewer_missing_account_label=Contact account not in persona",
"ContactArtifactViewer_found_all_accounts_label=All contact accounts are in persona"
})
private void showPersona(Persona persona, List<CentralRepoAccount> missingAccountsList, GridBagLayout gridBagLayout, GridBagConstraints constraints) {
Insets savedInsets = constraints.insets;
Insets labelInsets = new java.awt.Insets(0, LEFT_INSET, 0, 0);
//constraints.fill = GridBagConstraints.NONE;
//constraints.weightx = 0;
//constraints.gridx = 0;
//javax.swing.Box.Filler filler1 = createFiller(5, 0);
// gridBagLayout.setConstraints(filler1, constraints);
//personasPanel.add(filler1);
// Add a "Persona" label
//constraints.gridx++;
// javax.swing.JLabel personaLabel = new javax.swing.JLabel();
// personaLabel.setText(Bundle.ContactArtifactViewer_persona_label());
// personaLabel.setFont(personaLabel.getFont().deriveFont(Font.BOLD, personaLabel.getFont().getSize()));
// gridBagLayout.setConstraints(personaLabel, constraints);
// personasPanel.add(personaLabel);
javax.swing.JLabel personaNameLabel = new javax.swing.JLabel();
javax.swing.JButton personaButton = new javax.swing.JButton();
String personaName;
String personaButtonText;
ActionListener personaButtonListener;
if (persona != null) {
personaName = persona.getName();
personaButtonText = Bundle.ContactArtifactViewer_persona_button_view();
personaButtonListener = new ViewPersonaButtonListener(this, persona);
} else {
personaName = Bundle.ContactArtifactViewer_persona_name_unknown();
personaButtonText = Bundle.ContactArtifactViewer_persona_button_new();
personaButtonListener = new CreatePersonaButtonListener(this, new PersonaUIComponents(personaNameLabel, personaButton));
}
// Add the label for persona name,
constraints.insets = labelInsets;
constraints.gridx = 0;
constraints.gridwidth = 2; // TBD: this may not be needed if we use single panel
personaNameLabel.setText(personaName);
gridBagLayout.setConstraints(personaNameLabel, constraints);
personasPanel.add(personaNameLabel);
// Add a Persona action button
constraints.gridx += 2;
constraints.gridwidth = 1;
personaButton.setText(personaButtonText);
personaButton.addActionListener(personaButtonListener);
constraints.insets = new java.awt.Insets(0, LEFT_INSET, 0, 0);
gridBagLayout.setConstraints(personaButton, constraints);
personasPanel.add(personaButton);
constraints.insets = labelInsets;
// if we have a persona, indicate if any of the contact's accounts are missing from it.
if (persona != null) {
if (missingAccountsList.isEmpty()) {
constraints.gridy++;
CommunicationArtifactViewerHelper.addKeyAtCol(personasPanel, gridBagLayout, constraints, Bundle.ContactArtifactViewer_found_all_accounts_label(), 2);
} else {
// show missing accounts.
for (CentralRepoAccount missingAccount : missingAccountsList) {
constraints.weightx = 0;
constraints.gridx = 0;
constraints.gridy++;
CommunicationArtifactViewerHelper.addKeyAtCol(personasPanel, gridBagLayout, constraints, Bundle.ContactArtifactViewer_missing_account_label(), 2);
CommunicationArtifactViewerHelper.addValueAtCol(personasPanel, gridBagLayout, constraints, missingAccount.getIdentifier(), 3);
}
}
}
// restore insets
constraints.insets = savedInsets;
}
/** /**
* Resets all artifact specific state. * Resets all artifact specific state.
*/ */
private void resetComponent() { private void resetComponent() {
contactArtifact = null;
contactName = null; contactName = null;
datasourceName = null;
contactUniqueAccountsList.clear(); contactUniqueAccountsList.clear();
contactUniquePersonasMap.clear(); contactUniquePersonasMap.clear();
phoneNumList.clear();
emailList.clear();
nameList.clear();
otherList.clear();
accountAttributesList.clear();
if (personaSearchTask != null) { if (personaSearchTask != null) {
personaSearchTask.cancel(); personaSearchTask.cancel(Boolean.TRUE);
personaSearchTask = null;
} }
personaSearchTask - mull; this.contactDetailsPanel.removeAll();
this.personasPanel.removeAll();
// clear the panel this.sourcePanel.removeAll();
this.removeAll();
this.setLayout(null);
m_gridBagLayout = new GridBagLayout();
m_constraints = new GridBagConstraints();
m_constraints.anchor = GridBagConstraints.FIRST_LINE_START;
m_constraints.gridy = 0;
m_constraints.gridx = 0;
m_constraints.weighty = 0.05;
m_constraints.weightx = 0.05;
m_constraints.insets = new java.awt.Insets(0, 0, 0, 0);
m_constraints.fill = GridBagConstraints.NONE;
} }
/** /**
@ -404,7 +651,6 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
return imageIcon; return imageIcon;
} }
/** /**
* Thread to search for a personas for all account identifier attributes for * Thread to search for a personas for all account identifier attributes for
* a contact. * a contact.
@ -429,6 +675,7 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
Map<Persona, ArrayList<CentralRepoAccount>> uniquePersonas = new HashMap<>(); Map<Persona, ArrayList<CentralRepoAccount>> uniquePersonas = new HashMap<>();
// TBD: this search needs to change to use the new method CommunicationsManager.getAccountsRelatedToArtifact
for (BlackboardAttribute bba : accountAttributesList) { for (BlackboardAttribute bba : accountAttributesList) {
// Get account, add to accounts list // Get account, add to accounts list
@ -462,7 +709,6 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
} }
} }
} }
} }
return uniquePersonas; return uniquePersonas;
@ -484,10 +730,7 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
contactUniqueAccountsList.clear(); contactUniqueAccountsList.clear();
contactUniqueAccountsList.addAll(uniqueAccountsList); contactUniqueAccountsList.addAll(uniqueAccountsList);
updatePersonasSection(); updatePersonas();
// also update the source section now
} catch (CancellationException ex) { } catch (CancellationException ex) {
logger.log(Level.INFO, "Persona searching was canceled."); //NON-NLS logger.log(Level.INFO, "Persona searching was canceled."); //NON-NLS
@ -500,6 +743,170 @@ public class ContactArtifactViewerNew extends javax.swing.JPanel implements Arti
} }
} }
/**
* A wrapper class that bags the UI components that need to be updated when
* a persona search task or a create dialog returns.
*/
private class PersonaUIComponents {
private final JLabel personaNameLabel;
private final JButton personaActionButton;
/**
* Constructor.
*
* @param personaNameLabel Persona name label.
* @param personaActionButton Persona action button.
*/
PersonaUIComponents(JLabel personaNameLabel, JButton personaActionButton) {
this.personaNameLabel = personaNameLabel;
this.personaActionButton = personaActionButton;
}
/**
* Returns persona name label.
*
* @return Persona name label.
*/
public JLabel getPersonaNameLabel() {
return personaNameLabel;
}
/**
* Returns persona action button.
*
* @return Persona action button.
*/
public JButton getPersonaActionButton() {
return personaActionButton;
}
}
/**
* Action listener for Create persona button.
*/
private class CreatePersonaButtonListener implements ActionListener {
private final Component parentComponent;
private final PersonaUIComponents personaUIComponents;
/**
* Constructs a listener for Create persona button..
*
* @param personaUIComponents UI components.
*/
CreatePersonaButtonListener(Component parentComponent, PersonaUIComponents personaUIComponents) {
this.personaUIComponents = personaUIComponents;
this.parentComponent = parentComponent;
}
@NbBundle.Messages({
"ContactArtifactViewer_persona_account_justification=Account found in Contact artifact"
})
@Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
// Launch the Persona Create dialog - do not display immediately
PersonaDetailsDialog createPersonaDialog = new PersonaDetailsDialog(parentComponent,
PersonaDetailsMode.CREATE, null, new PersonaCreateCallbackImpl(parentComponent, personaUIComponents), false);
// Pre populate the persona name and accounts if we have them.
PersonaDetailsPanel personaPanel = createPersonaDialog.getDetailsPanel();
if (contactName != null) {
personaPanel.setPersonaName(contactName);
}
// pass the list of accounts to the dialog
for (CentralRepoAccount account : contactUniqueAccountsList) {
personaPanel.addAccount(account, Bundle.ContactArtifactViewer_persona_account_justification(), Persona.Confidence.HIGH);
}
// display the dialog now
createPersonaDialog.display();
}
}
/**
* Action listener for View persona button.
*/
private class ViewPersonaButtonListener implements ActionListener {
private final Persona persona;
private final Component parentComponent;
/**
* Creates listener for View persona button.
*
* @param persona
*/
ViewPersonaButtonListener(Component parentComponent, Persona persona) {
this.persona = persona;
this.parentComponent = parentComponent;
}
@Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
new PersonaDetailsDialog(parentComponent,
PersonaDetailsMode.VIEW, persona, new PersonaViewCallbackImpl());
}
}
/**
* Callback method for the create mode of the PersonaDetailsDialog
*/
class PersonaCreateCallbackImpl implements PersonaDetailsDialogCallback {
private final Component parentComponent;
private final PersonaUIComponents personaUIComponents;
/**
* Creates a callback to handle new persona creation.
*
* @param personaUIComponents UI Components.
*/
PersonaCreateCallbackImpl(Component parentComponent, PersonaUIComponents personaUIComponents) {
this.parentComponent = parentComponent;
this.personaUIComponents = personaUIComponents;
}
@Override
public void callback(Persona persona) {
JButton personaButton = personaUIComponents.getPersonaActionButton();
if (persona != null) {
// update the persona name label with newly created persona,
// and change the button to a "View" button
personaUIComponents.getPersonaNameLabel().setText(persona.getName());
personaUIComponents.getPersonaActionButton().setText(Bundle.ContactArtifactViewer_persona_button_view());
// replace action listener with a View button listener
for (ActionListener act : personaButton.getActionListeners()) {
personaButton.removeActionListener(act);
}
personaButton.addActionListener(new ViewPersonaButtonListener(parentComponent, persona));
}
personaButton.getParent().revalidate();
personaButton.getParent().repaint();
}
}
/**
* Callback method for the view mode of the PersonaDetailsDialog
*/
class PersonaViewCallbackImpl implements PersonaDetailsDialogCallback {
@Override
public void callback(Persona persona) {
// nothing to do
}
}
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel contactDetailsPanel;
private javax.swing.Box.Filler filler1;
private javax.swing.JPanel personasPanel;
private javax.swing.JPanel sourcePanel;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
} }

View File

@ -1,5 +1,5 @@
#Updated by build script #Updated by build script
#Fri, 12 Jun 2020 14:50:38 -0400 #Fri, 19 Jun 2020 10:14:47 -0400
LBL_splash_window_title=Starting Autopsy LBL_splash_window_title=Starting Autopsy
SPLASH_HEIGHT=314 SPLASH_HEIGHT=314
SPLASH_WIDTH=538 SPLASH_WIDTH=538

View File

@ -1,4 +1,4 @@
#Updated by build script #Updated by build script
#Fri, 12 Jun 2020 14:50:38 -0400 #Fri, 19 Jun 2020 10:14:47 -0400
CTL_MainWindow_Title=Autopsy 4.15.0 CTL_MainWindow_Title=Autopsy 4.15.0
CTL_MainWindow_Title_No_Project=Autopsy 4.15.0 CTL_MainWindow_Title_No_Project=Autopsy 4.15.0