org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,24 +20,30 @@ package org.sleuthkit.autopsy.contentviewers;
import java.awt.Component;
import java.awt.Cursor;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
+import java.util.stream.Stream;
import javax.swing.SwingWorker;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.Node;
-import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
+import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerHtmlStyles;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
-import org.sleuthkit.autopsy.datamodel.ContentUtils;
+import org.sleuthkit.autopsy.coreutils.EscapeUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.TimeZoneUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
+import org.sleuthkit.datamodel.DataArtifact;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.FsContent;
@@ -49,7 +55,7 @@ import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
* shows the same data that can also be found in the ResultViewer table, just a
* different order and allows the full path to be visible in the bottom area.
*/
-@ServiceProvider(service = DataContentViewer.class, position = 6)
+@ServiceProvider(service = DataContentViewer.class, position = 4)
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public class Metadata extends javax.swing.JPanel implements DataContentViewer {
@@ -63,6 +69,7 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
public Metadata() {
initComponents();
customizeComponents();
+ ContentViewerHtmlStyles.setupHtmlJTextPane(jTextPane1);
}
/**
@@ -80,8 +87,6 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
setPreferredSize(new java.awt.Dimension(100, 52));
- jScrollPane2.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
- jScrollPane2.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
jScrollPane2.setPreferredSize(new java.awt.Dimension(610, 52));
jTextPane1.setEditable(false);
@@ -116,30 +121,59 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
* selectAllMenuItem.addActionListener(actList);
*/
- Utilities.configureTextPaneAsHtml(jTextPane1);
}
private void setText(String str) {
- jTextPane1.setText("" + str + ""); //NON-NLS
+ ContentViewerHtmlStyles.setupHtmlJTextPane(jTextPane1);
+ jTextPane1.setText("" + str + ""); //NON-NLS
+ }
+
+ private void addHeader(StringBuilder sb, String header, boolean spaced) {
+ sb.append(MessageFormat.format("{2}
",
+ (spaced) ? ContentViewerHtmlStyles.getSpacedSectionClassName() : "",
+ ContentViewerHtmlStyles.getHeaderClassName(),
+ header));
}
private void startTable(StringBuilder sb) {
- sb.append(""); //NON-NLS
+ sb.append(MessageFormat.format("",
+ ContentViewerHtmlStyles.getIndentedClassName())); //NON-NLS
}
private void endTable(StringBuilder sb) {
- sb.append("
"); //NON-NLS
+ sb.append("
"); //NON-NLS
}
private void addRow(StringBuilder sb, String key, String value) {
- sb.append("| "); //NON-NLS
- sb.append(key);
- sb.append(" | "); //NON-NLS
- sb.append(value);
- sb.append(" |
"); //NON-NLS
+ sb.append(MessageFormat.format("| {2}: | {4} |
",
+ ContentViewerHtmlStyles.getKeyColumnClassName(),
+ ContentViewerHtmlStyles.getTextClassName(),
+ EscapeUtil.escapeHtml(key),
+ ContentViewerHtmlStyles.getTextClassName(),
+ EscapeUtil.escapeHtml(value)
+ ));
+ }
+
+ private void addMonospacedRow(StringBuilder sb, String key) {
+ sb.append(MessageFormat.format("| {2} |
",
+ ContentViewerHtmlStyles.getKeyColumnClassName(),
+ ContentViewerHtmlStyles.getMonospacedClassName(),
+ EscapeUtil.escapeHtml(key)
+ ));
+ }
+
+ private void addRowWithMultipleValues(StringBuilder sb, String key, String[] values) {
+ String[] safeValues = values == null || values.length < 1 ? new String[]{""} : values;
+
+ addRow(sb, key, safeValues[0]);
+ Stream.of(safeValues)
+ .skip(1)
+ .filter(line -> line != null)
+ .forEach(line -> addRow(sb, "", EscapeUtil.escapeHtml(line)));
}
@Messages({
+ "Metadata.headerTitle=Metadata",
"Metadata.tableRowTitle.mimeType=MIME Type",
"Metadata.nodeText.truncated=(results truncated)",
"Metadata.tableRowTitle.sha1=SHA1",
@@ -219,8 +253,11 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
if (StringUtils.isEmpty(details)) {
details = Bundle.Metadata_nodeText_unknown();
}
- details = details.replaceAll("\n", "
");
- addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.acquisitionDetails"), details);
+ String[] lines = (details != null) ? details.split("\n") : new String[]{""};
+ addRowWithMultipleValues(sb,
+ NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.acquisitionDetails"),
+ lines);
+
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error reading acquisition details from case database", ex); //NON-NLS
}
@@ -229,7 +266,19 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
@Override
public String getTitle() {
- return NbBundle.getMessage(this.getClass(), "Metadata.title");
+ return getTitle(null);
+ }
+
+ @Messages({
+ "Metadata_dataArtifactTitle=Source File Metadata"
+ })
+ @Override
+ public String getTitle(Node node) {
+ if (node != null && !node.getLookup().lookupAll(DataArtifact.class).isEmpty()) {
+ return Bundle.Metadata_dataArtifactTitle();
+ } else {
+ return NbBundle.getMessage(this.getClass(), "Metadata.title");
+ }
}
@Override
@@ -275,6 +324,7 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
this.node = node;
}
+ @Messages("MetadataWorker.doInBackground.noDataMsg=No Data")
@Override
protected String doInBackground() throws Exception {
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
@@ -285,6 +335,7 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
}
StringBuilder sb = new StringBuilder();
+ addHeader(sb, Bundle.Metadata_headerTitle(), false);
startTable(sb);
if (file != null) {
@@ -299,10 +350,10 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.size"), Long.toString(file.getSize()));
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.fileNameAlloc"), file.getDirFlagAsString());
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.metadataAlloc"), file.getMetaFlagsAsString());
- addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.modified"), ContentUtils.getStringTime(file.getMtime(), file));
- addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.accessed"), ContentUtils.getStringTime(file.getAtime(), file));
- addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.created"), ContentUtils.getStringTime(file.getCrtime(), file));
- addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.changed"), ContentUtils.getStringTime(file.getCtime(), file));
+ addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.modified"), TimeZoneUtils.getFormattedTime(file.getMtime()));
+ addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.accessed"), TimeZoneUtils.getFormattedTime(file.getAtime()));
+ addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.created"), TimeZoneUtils.getFormattedTime(file.getCrtime()));
+ addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.changed"), TimeZoneUtils.getFormattedTime(file.getCtime()));
String md5 = file.getMd5Hash();
if (md5 == null) {
@@ -343,30 +394,43 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
* If we have a file system file, grab the more detailed
* metadata text too
*/
- try {
- if (file instanceof FsContent) {
- FsContent fsFile = (FsContent) file;
+ if (file instanceof FsContent) {
+ FsContent fsFile = (FsContent) file;
- sb.append("
\n"); //NON-NLS
- sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.text"));
- sb.append("
"); // NON-NLS
- for (String str : fsFile.getMetaDataText()) {
- sb.append(str).append("
"); //NON-NLS
+ addHeader(sb, NbBundle.getMessage(this.getClass(), "Metadata.nodeText.text"), true);
+
+ List istatStrings = Collections.emptyList();
+ try {
+ istatStrings = fsFile.getMetaDataText();
+ } catch (TskCoreException ex) {
+ istatStrings = Arrays.asList(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.exceptionNotice.text") + ex.getLocalizedMessage());
+ }
+
+ if (istatStrings.isEmpty() || (istatStrings.size() == 1 && StringUtils.isEmpty(istatStrings.get(0)))) {
+ sb.append(MessageFormat.format("{2}
",
+ ContentViewerHtmlStyles.getIndentedClassName(),
+ ContentViewerHtmlStyles.getTextClassName(),
+ Bundle.MetadataWorker_doInBackground_noDataMsg()));
+ } else {
+ startTable(sb);
+
+ for (String str : istatStrings) {
+ addMonospacedRow(sb, str);
/*
* Very long results can cause the UI to hang before
* displaying, so truncate the results if necessary.
*/
if (sb.length() > 50000) {
- sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.truncated"));
+ addMonospacedRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.nodeText.truncated"));
break;
}
}
- sb.append("\n"); //NON-NLS
+
+ endTable(sb);
}
- } catch (TskCoreException ex) {
- sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.exceptionNotice.text")).append(ex.getLocalizedMessage());
}
+
} else {
try {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.name"), image.getUniquePath());
@@ -405,20 +469,17 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
// Add all the data source paths to the "Local Path" value cell.
String[] imagePaths = image.getPaths();
+
if (imagePaths.length > 0) {
- StringBuilder pathValues = new StringBuilder("
");
- pathValues.append(imagePaths[0]);
- pathValues.append("
");
- for (int i = 1; i < imagePaths.length; i++) {
- pathValues.append("
");
- pathValues.append(imagePaths[i]);
- pathValues.append("
");
- }
- addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.localPath"), pathValues.toString());
+ addRowWithMultipleValues(sb,
+ NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.localPath"),
+ imagePaths);
} else {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.localPath"),
NbBundle.getMessage(this.getClass(), "Metadata.nodeText.none"));
}
+
+ endTable(sb);
}
if (isCancelled()) {
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.form
new file mode 100644
index 0000000000..98fb50c89a
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.form
@@ -0,0 +1,54 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.java
new file mode 100644
index 0000000000..d19cf80b5c
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.java
@@ -0,0 +1,285 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 Basis Technology Corp.
+ * Contact: carrier
sleuthkit 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.analysisresults;
+
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.Optional;
+import org.apache.commons.lang3.tuple.Pair;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.openide.util.NbBundle;
+import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.contentviewers.analysisresults.AnalysisResultsViewModel.NodeResults;
+import org.sleuthkit.autopsy.contentviewers.analysisresults.AnalysisResultsViewModel.ResultDisplayAttributes;
+import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerHtmlStyles;
+import org.sleuthkit.autopsy.coreutils.EscapeUtil;
+import org.sleuthkit.datamodel.AnalysisResult;
+import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.Score;
+
+/**
+ * Displays a list of analysis results in a panel.
+ */
+public class AnalysisResultsContentPanel extends javax.swing.JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String EMPTY_HTML = "";
+
+ // Anchors are inserted into the navigation so that the viewer can navigate to a selection.
+ // This is the prefix of those anchors.
+ private static final String RESULT_ANCHOR_PREFIX = "AnalysisResult_";
+
+ /**
+ * Creates new form AnalysisResultsContentViewer
+ */
+ public AnalysisResultsContentPanel() {
+ initComponents();
+ ContentViewerHtmlStyles.setupHtmlJTextPane(textPanel);
+ }
+
+ /**
+ * Clears current text and shows text provided in the message.
+ *
+ * @param message The message to be displayed.
+ */
+ void showMessage(String message) {
+ ContentViewerHtmlStyles.setStyles(textPanel);
+ textPanel.setText(""
+ + MessageFormat.format("{1}
",
+ ContentViewerHtmlStyles.getMessageClassName(),
+ message == null ? "" : EscapeUtil.escapeHtml(message))
+ + "");
+ }
+
+ /**
+ * Resets the current view and displays nothing.
+ */
+ void reset() {
+ textPanel.setText(EMPTY_HTML);
+ }
+
+ /**
+ * Displays analysis results for the node in the text pane.
+ *
+ * @param nodeResults The analysis results data to display.
+ */
+ void displayResults(NodeResults nodeResults) {
+ Document document = Jsoup.parse(EMPTY_HTML);
+ Element body = document.getElementsByTag("body").first();
+
+ Optional panelHeader = appendPanelHeader(body, nodeResults.getContent(), nodeResults.getAggregateScore());
+
+ // for each analysis result item, display the data.
+ List displayAttributes = nodeResults.getAnalysisResults();
+ for (int idx = 0; idx < displayAttributes.size(); idx++) {
+ AnalysisResultsViewModel.ResultDisplayAttributes resultAttrs = displayAttributes.get(idx);
+ Element sectionDiv = appendResult(body, idx, resultAttrs);
+ if (idx > 0 || panelHeader.isPresent()) {
+ sectionDiv.attr("class", ContentViewerHtmlStyles.getSpacedSectionClassName());
+ }
+ }
+
+ // set the body html
+ ContentViewerHtmlStyles.setStyles(textPanel);
+ textPanel.setText(document.html());
+
+ // if there is a selected result scroll to it
+ Optional selectedResult = nodeResults.getSelectedResult();
+ if (selectedResult.isPresent()) {
+ textPanel.scrollToReference(getAnchor(selectedResult.get()));
+ } else {
+ // otherwise, scroll to the beginning.
+ textPanel.setCaretPosition(0);
+ }
+ }
+
+ /**
+ * Appends the header to the panel.
+ *
+ * @param parent The parent html element.
+ * @param content The content whose name will be added if present.
+ * @param score The aggregate score whose significance will be added if
+ * present.
+ *
+ * @return The html element.
+ */
+ @Messages({
+ "AnalysisResultsContentPanel_aggregateScore_displayKey=Aggregate Score",
+ "AnalysisResultsContentPanel_content_displayKey=Item"
+ })
+ private Optional appendPanelHeader(Element parent, Optional content, Optional score) {
+ if (!content.isPresent() || !score.isPresent()) {
+ return Optional.empty();
+ }
+
+ Element container = parent.appendElement("div");
+
+ // if there is content append the name
+ content.ifPresent((c) -> {
+ container.appendElement("p")
+ .attr("class", ContentViewerHtmlStyles.getTextClassName())
+ .text(MessageFormat.format("{0}: {1}",
+ Bundle.AnalysisResultsContentPanel_content_displayKey(),
+ c.getName()));
+ });
+
+ // if there is an aggregate score, append the value
+ score.ifPresent((s) -> {
+ container.appendElement("p")
+ .attr("class", ContentViewerHtmlStyles.getTextClassName())
+ .text(MessageFormat.format("{0}: {1}",
+ Bundle.AnalysisResultsContentPanel_aggregateScore_displayKey(),
+ s.getSignificance().getDisplayName()));
+ });
+
+ return Optional.ofNullable(container);
+ }
+
+ /**
+ * Returns the anchor id to use with the analysis result (based on the id).
+ *
+ * @param analysisResult The analysis result.
+ *
+ * @return The anchor id.
+ */
+ private String getAnchor(AnalysisResult analysisResult) {
+ return RESULT_ANCHOR_PREFIX + analysisResult.getId();
+ }
+
+ /**
+ * Appends a result item to the parent element of an html document.
+ *
+ * @param parent The parent element.
+ * @param index The index of the item in the list of all items.
+ * @param attrs The attributes of this item.
+ *
+ * @return The result div.
+ */
+ @NbBundle.Messages({"# {0} - analysisResultsNumber",
+ "AnalysisResultsContentPanel_result_headerKey=Analysis Result {0}"
+ })
+ private Element appendResult(Element parent, int index, AnalysisResultsViewModel.ResultDisplayAttributes attrs) {
+ // create a new section with appropriate header
+ Element sectionDiv = appendSection(parent,
+ Bundle.AnalysisResultsContentPanel_result_headerKey(index + 1),
+ Optional.ofNullable(getAnchor(attrs.getAnalysisResult())));
+
+ // create a table
+ Element table = sectionDiv.appendElement("table")
+ .attr("valign", "top")
+ .attr("align", "left");
+
+ table.attr("class", ContentViewerHtmlStyles.getIndentedClassName());
+
+ Element tableBody = table.appendElement("tbody");
+
+ // append a row for each item
+ for (Pair keyVal : attrs.getAttributesToDisplay()) {
+ Element row = tableBody.appendElement("tr");
+ String keyString = keyVal.getKey() == null ? "" : keyVal.getKey() + ":";
+ Element keyTd = row.appendElement("td")
+ .attr("class", ContentViewerHtmlStyles.getKeyColumnClassName());
+
+ keyTd.appendElement("span")
+ .text(keyString)
+ .attr("class", ContentViewerHtmlStyles.getTextClassName());
+
+ String valueString = keyVal.getValue() == null ? "" : keyVal.getValue();
+ row.appendElement("td")
+ .text(valueString)
+ .attr("class", ContentViewerHtmlStyles.getTextClassName());
+ }
+
+ return sectionDiv;
+ }
+
+ /**
+ * Appends a new section with a section header to the parent element.
+ *
+ * @param parent The element to append this section to.
+ * @param headerText The text for the section.
+ * @param anchorId The anchor id for this section.
+ *
+ * @return The div for the new section.
+ */
+ private Element appendSection(Element parent, String headerText, Optional anchorId) {
+ Element sectionDiv = parent.appendElement("div");
+
+ // append an anchor tag if there is one
+ Element anchorEl = null;
+ if (anchorId.isPresent()) {
+ anchorEl = sectionDiv.appendElement("a");
+ anchorEl.attr("name", anchorId.get());
+ anchorEl.attr("style", "padding: 0px; margin: 0px; display: inline-block;");
+ }
+
+ // append the header
+ Element header = null;
+ header = (anchorEl == null)
+ ? sectionDiv.appendElement("h1")
+ : anchorEl.appendElement("h1");
+
+ header.text(headerText);
+ header.attr("class", ContentViewerHtmlStyles.getHeaderClassName());
+ header.attr("style", "display: inline-block");
+
+ // return the section element
+ return sectionDiv;
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane();
+ textPanel = new javax.swing.JTextPane();
+
+ setPreferredSize(new java.awt.Dimension(100, 58));
+
+ textPanel.setEditable(false);
+ textPanel.setName(""); // NOI18N
+ textPanel.setPreferredSize(new java.awt.Dimension(600, 52));
+ scrollPane.setViewportView(textPanel);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 907, Short.MAX_VALUE)
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(scrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 435, Short.MAX_VALUE)
+ );
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTextPane textPanel;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentViewer.java
new file mode 100644
index 0000000000..f853eae777
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentViewer.java
@@ -0,0 +1,159 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.analysisresults;
+
+import java.awt.Component;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.SwingWorker;
+import org.openide.nodes.Node;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
+import org.sleuthkit.datamodel.AnalysisResult;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ * Displays a list of analysis results as a content viewer.
+ */
+@ServiceProvider(service = DataContentViewer.class, position = 7)
+public class AnalysisResultsContentViewer implements DataContentViewer {
+ private static final Logger logger = Logger.getLogger(AnalysisResultsContentPanel.class.getName());
+
+ // isPreferred value
+ private static final int PREFERRED_VALUE = 6;
+
+ private final AnalysisResultsViewModel viewModel = new AnalysisResultsViewModel();
+ private final AnalysisResultsContentPanel panel = new AnalysisResultsContentPanel();
+
+ private SwingWorker, ?> worker = null;
+
+
+
+ @NbBundle.Messages({
+ "AnalysisResultsContentViewer_title=Analysis Results"
+ })
+ @Override
+ public String getTitle() {
+ return Bundle.AnalysisResultsContentViewer_title();
+ }
+
+ @NbBundle.Messages({
+ "AnalysisResultsContentViewer_tooltip=Viewer for Analysis Results related to the selected node."
+ })
+ @Override
+ public String getToolTip() {
+ return Bundle.AnalysisResultsContentViewer_tooltip();
+ }
+
+ @Override
+ public DataContentViewer createInstance() {
+ return new AnalysisResultsContentViewer();
+ }
+
+ @Override
+ public Component getComponent() {
+ return panel;
+ }
+
+ @Override
+ public void resetComponent() {
+ panel.reset();
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "AnalysisResultsContentViewer_setNode_loadingMessage=Loading...",
+ "AnalysisResultsContentViewer_setNode_errorMessage=There was an error loading results.",})
+ public synchronized void setNode(Node node) {
+ // reset the panel
+ panel.reset();
+
+ // if there is a worker running, cancel it
+ if (worker != null) {
+ worker.cancel(true);
+ worker = null;
+ }
+
+ // if no node, nothing to do
+ if (node == null) {
+ return;
+ }
+
+ // show a loading message
+ panel.showMessage(Bundle.AnalysisResultsContentViewer_setNode_loadingMessage());
+
+ // create the worker
+ worker = new DataFetchWorker<>(
+ // load a view model from the node
+ (selectedNode) -> viewModel.getAnalysisResults(selectedNode),
+ (nodeAnalysisResults) -> {
+ if (nodeAnalysisResults.getResultType() == DataFetchResult.ResultType.SUCCESS) {
+ // if successful, display the results
+ panel.displayResults(nodeAnalysisResults.getData());
+ } else {
+ // if there was an error, display an error message
+ panel.showMessage(Bundle.AnalysisResultsContentViewer_setNode_errorMessage());
+ }
+ },
+ node);
+
+ // kick off the swing worker
+ worker.execute();
+ }
+
+ @Override
+ public boolean isSupported(Node node) {
+ if (node == null) {
+ return false;
+ }
+
+ // There needs to either be a file with an AnalysisResult or an AnalysisResult in the lookup.
+ for (Content content : node.getLookup().lookupAll(Content.class)) {
+ if (content instanceof AnalysisResult) {
+ return true;
+ }
+
+ if (content == null || content instanceof BlackboardArtifact) {
+ continue;
+ }
+
+ try {
+ if (Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard().hasAnalysisResults(content.getId())) {
+ return true;
+ }
+ } catch (NoCurrentCaseException | TskCoreException ex) {
+ logger.log(Level.SEVERE, "Unable to get analysis results for file with obj id " + content.getId(), ex);
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public int isPreferred(Node node) {
+ return PREFERRED_VALUE;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsViewModel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsViewModel.java
new file mode 100644
index 0000000000..00cc170b14
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsViewModel.java
@@ -0,0 +1,295 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.analysisresults;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.tuple.Pair;
+import org.openide.nodes.Node;
+import org.openide.util.NbBundle;
+import org.sleuthkit.datamodel.AnalysisResult;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.Score;
+import org.sleuthkit.datamodel.TskCoreException;
+
+/**
+ *
+ * Creates a representation of a list of analysis results gathered from a node.
+ */
+public class AnalysisResultsViewModel {
+
+ private static final Logger logger = Logger.getLogger(AnalysisResultsViewModel.class.getName());
+
+ /**
+ * The attributes to display for a particular Analysis Result.
+ */
+ static class ResultDisplayAttributes {
+
+ private final AnalysisResult analysisResult;
+ private final List> attributesToDisplay;
+
+ /**
+ * Constructor.
+ *
+ * @param analysisResult The analysis result which these attributes
+ * describe.
+ * @param attributesToDisplay The attributes to display in the order
+ * they should be displayed.
+ */
+ ResultDisplayAttributes(AnalysisResult analysisResult, List> attributesToDisplay) {
+ this.analysisResult = analysisResult;
+ this.attributesToDisplay = attributesToDisplay;
+ }
+
+ /**
+ * Returns the attributes to display.
+ *
+ * @return The attributes to display.
+ */
+ List> getAttributesToDisplay() {
+ return attributesToDisplay;
+ }
+
+ /**
+ * Returns the analysis result which these attributes describe.
+ *
+ * @return The analysis result.
+ */
+ AnalysisResult getAnalysisResult() {
+ return analysisResult;
+ }
+ }
+
+ /**
+ * The analysis results relating to a node (i.e. belonging to source content
+ * or directly in the lookup) to be displayed.
+ */
+ static class NodeResults {
+
+ private final List analysisResults;
+ private final Optional selectedResult;
+ private final Optional aggregateScore;
+ private final Optional content;
+
+ /**
+ * Constructor.
+ *
+ * @param analysisResults The analysis results to be displayed.
+ * @param selectedResult The selected analysis result or empty if none
+ * selected.
+ * @param aggregateScore The aggregate score or empty if no score.
+ * @param content The content associated with these results.
+ */
+ NodeResults(List analysisResults, Optional selectedResult, Optional aggregateScore, Optional content) {
+ this.analysisResults = analysisResults;
+ this.selectedResult = selectedResult;
+ this.aggregateScore = aggregateScore;
+ this.content = content;
+ }
+
+ /**
+ * Returns the analysis results to be displayed.
+ *
+ * @return The analysis results to be displayed.
+ */
+ List getAnalysisResults() {
+ return analysisResults;
+ }
+
+ /**
+ * Returns the selected analysis result or empty if none selected.
+ *
+ * @return The selected analysis result or empty if none selected.
+ */
+ Optional getSelectedResult() {
+ return selectedResult;
+ }
+
+ /**
+ * Returns the aggregate score or empty if no score.
+ *
+ * @return The aggregate score or empty if no score.
+ */
+ Optional getAggregateScore() {
+ return aggregateScore;
+ }
+
+ /**
+ * Returns the content associated with these results or empty if not
+ * present.
+ *
+ * @return The content associated with these results or empty if not
+ * present.
+ */
+ Optional getContent() {
+ return content;
+ }
+ }
+
+ /**
+ * Normalizes the value of an attribute of an analysis result for display
+ * purposes.
+ *
+ * @param originalAttrStr The original attribute value.
+ *
+ * @return The normalized value for display.
+ */
+ private String normalizeAttr(String originalAttrStr) {
+ return (originalAttrStr == null) ? "" : originalAttrStr.trim();
+ }
+
+ /**
+ * Returns the attributes to be displayed for an analysis result.
+ *
+ * @param analysisResult The analysis result.
+ *
+ * @return The attributes to be displayed.
+ */
+ @NbBundle.Messages({
+ "AnalysisResultsViewModel_displayAttributes_score=Score",
+ "AnalysisResultsViewModel_displayAttributes_type=Type",
+ "AnalysisResultsViewModel_displayAttributes_configuration=Configuration",
+ "AnalysisResultsViewModel_displayAttributes_conclusion=Conclusion"
+ })
+ private ResultDisplayAttributes getDisplayAttributes(AnalysisResult analysisResult) {
+ // The type of BlackboardArtifact.Type of the analysis result.
+ String type = "";
+ try {
+ type = normalizeAttr(analysisResult.getType().getDisplayName());
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Unable to get type for analysis result with id: " + analysisResult.getArtifactID(), ex);
+ }
+
+ // The standard attributes to display (score, type, configuration, conclusion)
+ Stream> baseAnalysisAttrs = Stream.of(
+ Pair.of(Bundle.AnalysisResultsViewModel_displayAttributes_score(),
+ normalizeAttr(analysisResult.getScore().getSignificance().getDisplayName())),
+ Pair.of(Bundle.AnalysisResultsViewModel_displayAttributes_type(),
+ normalizeAttr(type)),
+ Pair.of(Bundle.AnalysisResultsViewModel_displayAttributes_configuration(),
+ normalizeAttr(analysisResult.getConfiguration())),
+ Pair.of(Bundle.AnalysisResultsViewModel_displayAttributes_conclusion(),
+ normalizeAttr(analysisResult.getConclusion()))
+ );
+
+ // The BlackboardAttributes sorted by type display name.
+ Stream> blackboardAttributes = Stream.empty();
+ try {
+
+ blackboardAttributes = analysisResult.getAttributes().stream()
+ .filter(attr -> attr != null && attr.getAttributeType() != null && attr.getAttributeType().getDisplayName() != null)
+ .map(attr -> Pair.of(attr.getAttributeType().getDisplayName(), normalizeAttr(attr.getDisplayString())))
+ .sorted((a, b) -> a.getKey().compareToIgnoreCase(b.getKey()));
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Unable to get attributes for analysis result with id: " + analysisResult.getArtifactID(), ex);
+ }
+
+ // return the standard attributes along with the key value pairs of the BlackboardAttribute values.
+ List> allDisplayAttributes = Stream.concat(baseAnalysisAttrs, blackboardAttributes)
+ .collect(Collectors.toList());
+
+ return new ResultDisplayAttributes(analysisResult, allDisplayAttributes);
+ }
+
+ private List getOrderedDisplayAttributes(Collection analysisResults) {
+ return analysisResults.stream()
+ .filter(ar -> ar != null && ar.getScore() != null)
+ // reverse order to push more important scores to the top
+ .sorted((a, b) -> -a.getScore().compareTo(b.getScore()))
+ .map((ar) -> getDisplayAttributes(ar))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Returns the view model data representing the analysis results to be
+ * displayed for the node.
+ *
+ * @param node The node.
+ *
+ * @return The analysis results view model data to display.
+ */
+ NodeResults getAnalysisResults(Node node) {
+ if (node == null) {
+ return new NodeResults(Collections.emptyList(), Optional.empty(), Optional.empty(), Optional.empty());
+ }
+
+ Optional aggregateScore = Optional.empty();
+ Optional nodeContent = Optional.empty();
+ // maps id of analysis result to analysis result to prevent duplication
+ Map allAnalysisResults = new HashMap<>();
+ Optional selectedResult = Optional.empty();
+
+ // Find first content that is not an artifact within node
+ for (Content content : node.getLookup().lookupAll(Content.class)) {
+ if (content == null || content instanceof BlackboardArtifact) {
+ continue;
+ }
+
+ try {
+ nodeContent = Optional.of(content);
+
+ // get the aggregate score of that content
+ aggregateScore = Optional.ofNullable(content.getAggregateScore());
+
+ // and add all analysis results to mapping
+ content.getAllAnalysisResults().stream()
+ .forEach((ar) -> allAnalysisResults.put(ar.getArtifactID(), ar));
+
+ break;
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Unable to get analysis results for content with obj id " + content.getId(), ex);
+ }
+ }
+
+ // Find any analysis results in the node
+ Collection extends AnalysisResult> analysisResults = node.getLookup().lookupAll(AnalysisResult.class);
+ if (analysisResults.size() > 0) {
+
+ // get any items with a score
+ List filteredResults = analysisResults.stream()
+ .collect(Collectors.toList());
+
+ // add them to the map to display
+ filteredResults.forEach((ar) -> allAnalysisResults.put(ar.getArtifactID(), ar));
+
+ // the selected result will be the highest scored analysis result in the node.
+ selectedResult = filteredResults.stream()
+ .max((a, b) -> a.getScore().compareTo(b.getScore()));
+
+ // if no aggregate score determined at this point, use the selected result score.
+ if (!aggregateScore.isPresent()) {
+ aggregateScore = selectedResult.flatMap(selectedRes -> Optional.ofNullable(selectedRes.getScore()));
+ }
+ }
+
+ // get view model representation
+ List displayAttributes = getOrderedDisplayAttributes(allAnalysisResults.values());
+
+ return new NodeResults(displayAttributes, selectedResult, aggregateScore, nodeContent);
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/Bundle.properties-MERGED
new file mode 100644
index 0000000000..f4558602d8
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/Bundle.properties-MERGED
@@ -0,0 +1,11 @@
+AnalysisResultsContentPanel_aggregateScore_displayKey=Aggregate Score
+# {0} - analysisResultsNumber
+AnalysisResultsContentPanel_result_headerKey=Analysis Result {0}
+AnalysisResultsContentViewer_setNode_errorMessage=There was an error loading results.
+AnalysisResultsContentViewer_setNode_loadingMessage=Loading...
+AnalysisResultsContentViewer_title=Analysis Results
+AnalysisResultsContentViewer_tooltip=Viewer for Analysis Results related to the selected node.
+AnalysisResultsViewModel_displayAttributes_conclusion=Conclusion
+AnalysisResultsViewModel_displayAttributes_configuration=Configuration
+AnalysisResultsViewModel_displayAttributes_score=Score
+AnalysisResultsViewModel_displayAttributes_type=Type
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/Bundle_ja.properties
new file mode 100644
index 0000000000..14c0eb0f44
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/Bundle_ja.properties
@@ -0,0 +1,11 @@
+#Thu Jul 01 11:56:41 UTC 2021
+AnalysisResultsContentPanel_aggregateScore_displayKey=\u96c6\u8a08\u30b9\u30b3\u30a2
+AnalysisResultsContentPanel_result_headerKey=\u5206\u6790\u7d50\u679c{0}
+AnalysisResultsContentViewer_setNode_errorMessage=\u7d50\u679c\u306e\u8aad\u8fbc\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
+AnalysisResultsContentViewer_setNode_loadingMessage=\u8aad\u307f\u8fbc\u307f\u4e2d...
+AnalysisResultsContentViewer_title=\u5206\u6790\u7d50\u679c
+AnalysisResultsContentViewer_tooltip=\u9078\u629e\u3057\u305f\u30ce\u30fc\u30c9\u306b\u95a2\u9023\u3059\u308b\u5206\u6790\u7d50\u679c\u306e\u30d3\u30e5\u30fc\u30a2\u3002
+AnalysisResultsViewModel_displayAttributes_conclusion=\u7d50\u8ad6
+AnalysisResultsViewModel_displayAttributes_configuration=\u69cb\u6210
+AnalysisResultsViewModel_displayAttributes_score=\u30b9\u30b3\u30a2
+AnalysisResultsViewModel_displayAttributes_type=\u30bf\u30a4\u30d7
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/application/Annotations.java b/Core/src/org/sleuthkit/autopsy/contentviewers/application/Annotations.java
index feec2703dc..67124d1502 100755
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/application/Annotations.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/application/Annotations.java
@@ -24,7 +24,6 @@ import java.util.List;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
-import javax.swing.JLabel;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jsoup.Jsoup;
@@ -39,6 +38,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil;
+import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerHtmlStyles;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
@@ -80,18 +80,6 @@ public class Annotations {
private static final String EMPTY_HTML = "";
- private static final int DEFAULT_FONT_SIZE = new JLabel().getFont().getSize();
- // spacing occurring after an item
- private static final int DEFAULT_TABLE_SPACING = DEFAULT_FONT_SIZE;
-
- // html stylesheet classnames for components
- public static final String MESSAGE_CLASSNAME = "message";
- public static final String SUBSECTION_CLASSNAME = "subsection";
- public static final String SUBHEADER_CLASSNAME = "subheader";
- public static final String SECTION_CLASSNAME = "section";
- public static final String HEADER_CLASSNAME = "header";
- public static final String VERTICAL_TABLE_CLASSNAME = "vertical-table";
-
// describing table values for a tag
private static final List> TAG_ENTRIES = Arrays.asList(
new ItemEntry<>(Bundle.Annotations_tagEntryDataLabel_tag(),
@@ -200,11 +188,11 @@ public class Annotations {
* @return If any content was actually rendered.
*/
private static boolean renderArtifact(Element parent, BlackboardArtifact bba, Content sourceContent) {
- boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(bba), false);
+ boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(bba), false, true);
if (CentralRepository.isEnabled()) {
List centralRepoComments = getCentralRepositoryData(bba);
- boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, false);
+ boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, false, !contentRendered);
contentRendered = contentRendered || crRendered;
}
@@ -213,12 +201,17 @@ public class Annotations {
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() == bba.getArtifactTypeID())
&& (hasTskComment(bba))) {
- boolean filesetRendered = appendEntries(parent, ARTIFACT_COMMENT_CONFIG, Arrays.asList(bba), false);
+ boolean filesetRendered = appendEntries(parent, ARTIFACT_COMMENT_CONFIG, Arrays.asList(bba), false, !contentRendered);
contentRendered = contentRendered || filesetRendered;
}
Element sourceFileSection = appendSection(parent, Bundle.Annotations_sourceFile_title());
- boolean sourceFileRendered = renderContent(sourceFileSection, sourceContent, true);
+ sourceFileSection.attr("class", ContentViewerHtmlStyles.getSpacedSectionClassName());
+
+ Element sourceFileContainer = sourceFileSection.appendElement("div");
+ sourceFileContainer.attr("class", ContentViewerHtmlStyles.getIndentedClassName());
+
+ boolean sourceFileRendered = renderContent(sourceFileContainer, sourceContent, true);
if (!sourceFileRendered) {
sourceFileSection.remove();
@@ -238,24 +231,27 @@ public class Annotations {
* @return If any content was actually rendered.
*/
private static boolean renderContent(Element parent, Content sourceContent, boolean isSubheader) {
- boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(sourceContent), isSubheader);
+ boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(sourceContent), isSubheader, true);
if (sourceContent instanceof AbstractFile) {
AbstractFile sourceFile = (AbstractFile) sourceContent;
if (CentralRepository.isEnabled()) {
List centralRepoComments = getCentralRepositoryData(sourceFile);
- boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, isSubheader);
+ boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, isSubheader,
+ !contentRendered);
contentRendered = contentRendered || crRendered;
}
boolean hashsetRendered = appendEntries(parent, HASHSET_CONFIG,
getFileSetHits(sourceFile, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT),
- isSubheader);
+ isSubheader,
+ !contentRendered);
boolean interestingFileRendered = appendEntries(parent, INTERESTING_FILE_CONFIG,
getFileSetHits(sourceFile, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT),
- isSubheader);
+ isSubheader,
+ !contentRendered);
contentRendered = contentRendered || hashsetRendered || interestingFileRendered;
}
@@ -456,24 +452,36 @@ public class Annotations {
* will be formatted as a table in the format specified in the
* SectionConfig.
*
- * @param parent The parent element for which the entries will be
- * attached.
- * @param config The display configuration for this entry type (i.e.
- * table type, name, if data is not present).
- * @param items The items to display.
- * @param isSubsection Whether or not this should be displayed as a
- * subsection. If not displayed as a top-level section.
+ * @param parent The parent element for which the entries will be
+ * attached.
+ * @param config The display configuration for this entry type (i.e.
+ * table type, name, if data is not present).
+ * @param items The items to display.
+ * @param isSubsection Whether or not this should be displayed as a
+ * subsection. If not displayed as a top-level
+ * section.
+ * @param isFirstSection Whether or not this is the first section appended.
*
* @return If there was actual content rendered for this set of entries.
*/
private static boolean appendEntries(Element parent, Annotations.SectionConfig config, List extends T> items,
- boolean isSubsection) {
+ boolean isSubsection, boolean isFirstSection) {
if (items == null || items.isEmpty()) {
return false;
}
Element sectionDiv = (isSubsection) ? appendSubsection(parent, config.getTitle()) : appendSection(parent, config.getTitle());
- appendVerticalEntryTables(sectionDiv, items, config.getAttributes());
+ if (!isFirstSection) {
+ sectionDiv.attr("class", ContentViewerHtmlStyles.getSpacedSectionClassName());
+ }
+
+ Element sectionContainer = sectionDiv.appendElement("div");
+
+ if (!isSubsection) {
+ sectionContainer.attr("class", ContentViewerHtmlStyles.getIndentedClassName());
+ }
+
+ appendVerticalEntryTables(sectionContainer, items, config.getAttributes());
return true;
}
@@ -499,12 +507,11 @@ public class Annotations {
.collect(Collectors.toList());
Element childTable = appendTable(parent, 2, tableData, null);
- childTable.attr("class", VERTICAL_TABLE_CLASSNAME);
if (isFirst) {
isFirst = false;
} else {
- childTable.attr("style", String.format("margin-top: %dpx;", DEFAULT_TABLE_SPACING));
+ childTable.attr("class", ContentViewerHtmlStyles.getSpacedSectionClassName());
}
}
@@ -524,7 +531,10 @@ public class Annotations {
* @return The created table.
*/
private static Element appendTable(Element parent, int columnNumber, List> content, List columnHeaders) {
- Element table = parent.appendElement("table");
+ Element table = parent.appendElement("table")
+ .attr("valign", "top")
+ .attr("align", "left");
+
if (columnHeaders != null && !columnHeaders.isEmpty()) {
Element header = table.appendElement("thead");
appendRow(header, columnHeaders, columnNumber, true);
@@ -551,8 +561,15 @@ public class Annotations {
Element row = rowParent.appendElement("tr");
for (int i = 0; i < columnNumber; i++) {
Element cell = row.appendElement(cellType);
+
+ if (i == 0) {
+ cell.attr("class", ContentViewerHtmlStyles.getKeyColumnClassName());
+ }
+
if (data != null && i < data.size()) {
- cell.text(StringUtils.isEmpty(data.get(i)) ? "" : data.get(i));
+ cell.appendElement("span")
+ .attr("class", ContentViewerHtmlStyles.getTextClassName())
+ .text(StringUtils.isEmpty(data.get(i)) ? "" : data.get(i));
}
}
return row;
@@ -568,10 +585,9 @@ public class Annotations {
*/
private static Element appendSection(Element parent, String headerText) {
Element sectionDiv = parent.appendElement("div");
- sectionDiv.attr("class", SECTION_CLASSNAME);
Element header = sectionDiv.appendElement("h1");
header.text(headerText);
- header.attr("class", HEADER_CLASSNAME);
+ header.attr("class", ContentViewerHtmlStyles.getHeaderClassName());
return sectionDiv;
}
@@ -585,10 +601,9 @@ public class Annotations {
*/
private static Element appendSubsection(Element parent, String headerText) {
Element subsectionDiv = parent.appendElement("div");
- subsectionDiv.attr("class", SUBSECTION_CLASSNAME);
Element header = subsectionDiv.appendElement("h2");
header.text(headerText);
- header.attr("class", SUBHEADER_CLASSNAME);
+ header.attr("class", ContentViewerHtmlStyles.getHeaderClassName());
return subsectionDiv;
}
@@ -605,7 +620,7 @@ public class Annotations {
private static Element appendMessage(Element parent, String message) {
Element messageEl = parent.appendElement("p");
messageEl.text(message);
- messageEl.attr("class", MESSAGE_CLASSNAME);
+ messageEl.attr("class", ContentViewerHtmlStyles.getMessageClassName());
return messageEl;
}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/application/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/application/Bundle_ja.properties
new file mode 100644
index 0000000000..ad13ec51b3
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/application/Bundle_ja.properties
@@ -0,0 +1,19 @@
+#Thu Jul 01 11:56:41 UTC 2021
+Annotations.centralRepositoryEntry.title=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u30b3\u30e1\u30f3\u30c8
+Annotations.centralRepositoryEntryDataLabel.case=\u30b1\u30fc\u30b9\:
+Annotations.centralRepositoryEntryDataLabel.comment=\u30b3\u30e1\u30f3\u30c8\:
+Annotations.centralRepositoryEntryDataLabel.path=\u30d1\u30b9\:
+Annotations.centralRepositoryEntryDataLabel.type=\u30bf\u30a4\u30d7\:
+Annotations.fileHitEntry.artifactCommentTitle=\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u30b3\u30e1\u30f3\u30c8
+Annotations.fileHitEntry.comment=\u30b3\u30e1\u30f3\u30c8\:
+Annotations.fileHitEntry.hashSetHitTitle=\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u30fb\u30d2\u30c3\u30c8\u30b3\u30e1\u30f3\u30c8
+Annotations.fileHitEntry.interestingFileHitTitle=\u8208\u5473\u6df1\u3044\u30d5\u30a1\u30a4\u30eb\u30d2\u30c3\u30c8\u30b3\u30e1\u30f3\u30c8
+Annotations.fileHitEntry.setName=\u30bb\u30c3\u30c8\u540d\:
+Annotations.onEmpty=\u3053\u306e\u30a2\u30a4\u30c6\u30e0\u306e\u6ce8\u91c8\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002
+Annotations.sourceFile.title=\u30bd\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb
+Annotations.tagEntry.title=\u30bf\u30b0
+Annotations.tagEntryDataLabel.comment=\u30b3\u30e1\u30f3\u30c8\:
+Annotations.tagEntryDataLabel.tag=\u30bf\u30b0\:
+Annotations.tagEntryDataLabel.tagUser=\u5be9\u67fb\u5b98\uff1a
+Annotations.title=\u30a2\u30ce\u30c6\u30fc\u30b7\u30e7\u30f3
+Annotations.toolTip=\u9078\u629e\u3057\u305f\u30b3\u30f3\u30c6\u30f3\u30c4\u3068\u95a2\u9023\u4ed8\u3051\u3089\u308c\u3066\u3044\u308b\u30bf\u30b0\u3068\u30b3\u30e1\u30f3\u30c8\u3092\u8868\u793a\u3057\u307e\u3059\u3002
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CallLogArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CallLogArtifactViewer.java
index 8d27c6c5be..0830e8e6ef 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CallLogArtifactViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CallLogArtifactViewer.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2020 Basis Technology Corp.
+ * Copyright 2020-2021 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.contentviewers.artifactviewers;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
+import java.awt.Insets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -30,11 +31,13 @@ import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.JScrollPane;
+import javax.swing.border.EmptyBorder;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
+import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerDefaults;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.guiutils.ContactCache;
import org.sleuthkit.datamodel.BlackboardArtifact;
@@ -75,6 +78,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
*/
public CallLogArtifactViewer() {
initComponents();
+ this.setBorder(new EmptyBorder(ContentViewerDefaults.getPanelInsets()));
}
/**
@@ -93,29 +97,28 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
public void setArtifact(BlackboardArtifact artifact) {
resetComponent();
- if (artifact == null) {
- return;
- }
-
CallLogViewData callLogViewData = null;
try {
callLogViewData = getCallLogViewData(artifact);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error getting attributes for Calllog artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex);
}
-
+ List personaSearchDataList = new ArrayList<>();
// update the view with the call log data
if (callLogViewData != null) {
- List personaSearchDataList = updateView(callLogViewData);
- if (!personaSearchDataList.isEmpty()) {
- currentAccountFetcher = new PersonaAccountFetcher(artifact, personaSearchDataList, this);
- currentAccountFetcher.execute();
- } else {
- currentAccountFetcher = null;
- }
+ personaSearchDataList.addAll(updateView(callLogViewData));
+
}
+ if (!personaSearchDataList.isEmpty()) {
+ currentAccountFetcher = new PersonaAccountFetcher(artifact, personaSearchDataList, this);
+ currentAccountFetcher.execute();
+ } else {
+ currentAccountFetcher = null;
+ }
+
// repaint
this.revalidate();
+ this.repaint();
}
/**
@@ -309,13 +312,13 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
})
private List updateView(CallLogViewData callLogViewData) {
- CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_heading_parties());
+ CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, 0, Bundle.CallLogArtifactViewer_heading_parties());
List dataList = new ArrayList<>();
// Display "From" if we have non-local device accounts
if (callLogViewData.getFromAccount() != null) {
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_label_from());
-
+
// check if this is local account
String accountDisplayString = getAccountDisplayString(callLogViewData.getFromAccount(), callLogViewData);
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString);
@@ -366,8 +369,6 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
this.setLayout(m_gridBagLayout);
this.revalidate();
- this.repaint();
-
return dataList;
}
@@ -384,7 +385,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
})
private void updateMetadataView(CallLogViewData callLogViewData) {
- CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_heading_metadata());
+ CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, ContentViewerDefaults.getSectionSpacing(), Bundle.CallLogArtifactViewer_heading_metadata());
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_label_direction());
if (callLogViewData.getDirection() != null) {
@@ -414,7 +415,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
"CallLogArtifactViewer_heading_Source=Source",
"CallLogArtifactViewer_label_datasource=Data Source",})
private void updateSourceView(CallLogViewData callLogViewData) {
- CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_heading_Source());
+ CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, ContentViewerDefaults.getSectionSpacing(), Bundle.CallLogArtifactViewer_heading_Source());
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_label_datasource());
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, callLogViewData.getDataSourceName());
}
@@ -432,7 +433,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
if (callLogViewData.getOtherAttributes().isEmpty()) {
return;
}
- CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_heading_others());
+ CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, ContentViewerDefaults.getSectionSpacing(), Bundle.CallLogArtifactViewer_heading_others());
for (Map.Entry entry : callLogViewData.getOtherAttributes().entrySet()) {
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, entry.getKey());
@@ -444,9 +445,8 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
"CalllogArtifactViewer_cr_disabled_message=Enable Central Repository to view, create and edit personas."
})
private void showCRDisabledMessage() {
- CommunicationArtifactViewerHelper.addBlankLine(this, m_gridBagLayout, m_constraints);
- m_constraints.gridy++;
- CommunicationArtifactViewerHelper.addMessageRow(this, m_gridBagLayout, m_constraints, Bundle.ContactArtifactViewer_cr_disabled_message());
+ Insets messageInsets = new Insets(ContentViewerDefaults.getSectionSpacing(), 0, ContentViewerDefaults.getLineSpacing(), 0);
+ CommunicationArtifactViewerHelper.addMessageRow(this, m_gridBagLayout, messageInsets, m_constraints, Bundle.ContactArtifactViewer_cr_disabled_message());
m_constraints.gridy++;
}
@@ -505,7 +505,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
m_constraints.gridx = 0;
m_constraints.weighty = 0.0;
m_constraints.weightx = 0.0; // keep components fixed horizontally.
- m_constraints.insets = new java.awt.Insets(0, CommunicationArtifactViewerHelper.LEFT_INSET, 0, 0);
+ m_constraints.insets = new java.awt.Insets(0, ContentViewerDefaults.getSectionIndent(), 0, 0);
m_constraints.fill = GridBagConstraints.NONE;
}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java
index fe5e19aff5..6f7e4cff2f 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CommunicationArtifactViewerHelper.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2020 Basis Technology Corp.
+ * Copyright 2020-2021 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,7 +19,6 @@
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
import java.awt.Dimension;
-import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
@@ -38,6 +37,7 @@ import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
+import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerDefaults;
/**
*
@@ -49,8 +49,6 @@ final class CommunicationArtifactViewerHelper {
// Number of columns in the gridbag layout.
private final static int MAX_COLS = 4;
- final static int LEFT_INSET = 12;
-
/**
* Empty private constructor
*/
@@ -64,34 +62,34 @@ final class CommunicationArtifactViewerHelper {
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
* @param constraints Constraints to use.
+ * @param spacing Spacing to add to top insets (in pixels).
* @param headerString Heading string to display.
*
* @return JLabel Heading label added.
*/
- static JLabel addHeader(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String headerString) {
+ static JLabel addHeader(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, int topSpacing, String headerString) {
Insets savedInsets = constraints.insets;
// create label for heading
javax.swing.JLabel headingLabel = new javax.swing.JLabel();
- // add a blank line before the start of new section, unless it's
- // the first section
- if (constraints.gridy != 0) {
- addBlankLine(panel, gridbagLayout, constraints);
- }
+
constraints.gridy++;
constraints.gridx = 0;
// let the header span all of the row
constraints.gridwidth = MAX_COLS;
- constraints.insets = new Insets(0, 0, 0, 0); // No inset for header
+ constraints.anchor = GridBagConstraints.LINE_START;
+ constraints.fill = GridBagConstraints.NONE;
+
+ constraints.insets = new Insets(topSpacing, 0, ContentViewerDefaults.getLineSpacing(), 0);
// set text
- headingLabel.setText(headerString);
+ headingLabel.setText(headerString.trim());
// make it large and bold
- headingLabel.setFont(headingLabel.getFont().deriveFont(Font.BOLD, headingLabel.getFont().getSize() + 2));
+ headingLabel.setFont(ContentViewerDefaults.getHeaderFont());
// add to panel
gridbagLayout.setConstraints(headingLabel, constraints);
@@ -159,7 +157,7 @@ final class CommunicationArtifactViewerHelper {
int savedFill = constraints.fill;
constraints.weightx = 1.0; // take up all the horizontal space
- constraints.fill = GridBagConstraints.BOTH;
+ constraints.fill = GridBagConstraints.HORIZONTAL;
javax.swing.Box.Filler horizontalFiller = new javax.swing.Box.Filler(new Dimension(0, 0), new Dimension(0, 0), new Dimension(32767, 0));
gridbagLayout.setConstraints(horizontalFiller, constraints);
@@ -181,6 +179,7 @@ final class CommunicationArtifactViewerHelper {
static void addPageEndGlue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) {
constraints.gridx = 0;
+ constraints.gridy++;
double savedWeighty = constraints.weighty;
int savedFill = constraints.fill;
@@ -197,24 +196,6 @@ final class CommunicationArtifactViewerHelper {
constraints.fill = savedFill;
}
- /**
- * Adds a blank line to the panel.
- *
- * @param panel Panel to update.
- * @param gridbagLayout Layout to use.
- * @param constraints Constraints to use.
- */
- static void addBlankLine(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) {
- constraints.gridy++;
- constraints.gridx = 0;
-
- javax.swing.JLabel filler = new javax.swing.JLabel(" ");
- gridbagLayout.setConstraints(filler, constraints);
- panel.add(filler);
-
- addLineEndGlue(panel, gridbagLayout, constraints);
- }
-
/**
* Adds a label/key to the panel at col 0.
*
@@ -247,9 +228,12 @@ final class CommunicationArtifactViewerHelper {
constraints.gridy++;
constraints.gridx = gridx < MAX_COLS - 1 ? gridx : MAX_COLS - 2;
+ constraints.anchor = GridBagConstraints.LINE_START;
+ constraints.insets = new Insets(0, ContentViewerDefaults.getSectionIndent(), ContentViewerDefaults.getLineSpacing(), 0);
// set text
- keyLabel.setText(keyString + ": ");
+ String preppedKeyString = keyString == null ? null : keyString.trim() + ":";
+ keyLabel.setText(preppedKeyString);
// add to panel
gridbagLayout.setConstraints(keyLabel, constraints);
@@ -288,6 +272,7 @@ final class CommunicationArtifactViewerHelper {
JTextPane valueField = new JTextPane();
valueField.setEditable(false);
valueField.setOpaque(false);
+ valueField.setMargin(new Insets(0,0,0,0));
constraints.gridx = gridx < MAX_COLS ? gridx : MAX_COLS - 1;
@@ -295,7 +280,8 @@ final class CommunicationArtifactViewerHelper {
// let the value span 2 cols
cloneConstraints.gridwidth = 2;
- cloneConstraints.fill = GridBagConstraints.BOTH;
+ constraints.anchor = GridBagConstraints.LINE_START;
+ cloneConstraints.insets = new Insets(0, ContentViewerDefaults.getColumnSpacing(), ContentViewerDefaults.getLineSpacing(), 0);
// set text
valueField.setText(valueString);
@@ -325,13 +311,13 @@ final class CommunicationArtifactViewerHelper {
* @param panel Panel to show.
* @param gridbagLayout Layout to use.
* @param constraints Constraints to use.
- *
+ * @param insets The insets to be used for the grid bag layout constraints. If null, default insets are assumed.
* @param messageString Message to display.
*
* @return Label for message added.
*/
- static JLabel addMessageRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String messageString) {
- return addMessageRow(panel, gridbagLayout, constraints, messageString, 0);
+ static JLabel addMessageRow(JPanel panel, GridBagLayout gridbagLayout, Insets insets, GridBagConstraints constraints, String messageString) {
+ return addMessageRow(panel, gridbagLayout, constraints, insets, messageString, 0);
}
/**
@@ -340,26 +326,33 @@ final class CommunicationArtifactViewerHelper {
*
* @param panel Panel to show.
* @param gridbagLayout Layout to use.
+ * @param insets The insets to be used for the grid bag layout constraints.
* @param constraints Constraints to use.
- *
+ * @param insets The insets to be used for the grid bag layout constraints. If null, default insets are assumed.
* @param messageString Message to display.
+ * @param gridx The grid x location to use.
*
* @return Label for message added.
*/
- static JLabel addMessageRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String messageString, int gridx) {
+ static JLabel addMessageRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, Insets insets, String messageString, int gridx) {
// create label
javax.swing.JLabel messageLabel = new javax.swing.JLabel();
constraints.gridy++;
constraints.gridx = gridx < MAX_COLS - 1 ? gridx : MAX_COLS - 2;
-
+ constraints.insets = insets == null
+ ? new Insets(0, 0, ContentViewerDefaults.getLineSpacing(), 0) :
+ insets;
+ constraints.anchor = GridBagConstraints.LINE_START;
+
int savedGridwidth = constraints.gridwidth;
constraints.gridwidth = 3;
// set text
- messageLabel.setText(messageString);
+ messageLabel.setText(messageString == null ? null : messageString.trim());
+ messageLabel.setFont(ContentViewerDefaults.getMessageFont());
// add to panel
gridbagLayout.setConstraints(messageLabel, constraints);
@@ -406,8 +399,9 @@ final class CommunicationArtifactViewerHelper {
Insets savedInsets = constraints.insets;
// extra Indent in
- constraints.insets = new java.awt.Insets(0, 2 * LEFT_INSET, 0, 0);
-
+ constraints.insets = new java.awt.Insets(0, ContentViewerDefaults.getColumnSpacing(), ContentViewerDefaults.getLineSpacing(), 0);
+ constraints.anchor = GridBagConstraints.LINE_START;
+
// create label
javax.swing.JLabel personaLabel = new javax.swing.JLabel();
String personaLabelText = Bundle.CommunicationArtifactViewerHelper_persona_label();
@@ -415,15 +409,12 @@ final class CommunicationArtifactViewerHelper {
? Bundle.CommunicationArtifactViewerHelper_persona_searching()
: Bundle.CommunicationArtifactViewerHelper_persona_unknown());
- personaLabel.setText(personaLabelText);
-
+ personaLabel.setText(personaLabelText == null ? null : personaLabelText.trim());
+
// add to panel
gridbagLayout.setConstraints(personaLabel, constraints);
panel.add(personaLabel);
- // restore constraint
- constraints.insets = savedInsets;
-
constraints.gridx++;
// Place a button as place holder. It will be enabled when persona is available.
@@ -441,6 +432,9 @@ final class CommunicationArtifactViewerHelper {
} else {
personaLabel.setEnabled(false);
}
+
+ // restore constraint
+ constraints.insets = savedInsets;
addLineEndGlue(panel, gridbagLayout, constraints);
@@ -469,7 +463,7 @@ final class CommunicationArtifactViewerHelper {
GridBagConstraints indentedConstraints = (GridBagConstraints) constraints.clone();
// Add an indent to match persona labels
- indentedConstraints.insets = new java.awt.Insets(0, 2 * LEFT_INSET, 0, 0);
+ indentedConstraints.insets = new java.awt.Insets(0, ContentViewerDefaults.getSectionIndent(), ContentViewerDefaults.getLineSpacing(), 0);
String contactInfo = Bundle.CommunicationArtifactViewerHelper_contact_label(contactId != null && !contactId.isEmpty() ? contactId : Bundle.CommunicationArtifactViewerHelper_contact_label_unknown());
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java
index b17263a26d..e1181f80cd 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2020 Basis Technology Corp.
+ * Copyright 2020-2021 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -41,9 +41,9 @@ import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
-import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.SwingWorker;
+import javax.swing.border.EmptyBorder;
import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
@@ -57,6 +57,7 @@ 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.contentviewers.layout.ContentViewerDefaults;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Account;
@@ -64,6 +65,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.InvalidAccountIDException;
import org.sleuthkit.datamodel.TskCoreException;
/**
@@ -107,7 +109,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
*/
public ContactArtifactViewer() {
initComponents();
-
+ this.setBorder(new EmptyBorder(ContentViewerDefaults.getPanelInsets()));
defaultImage = new ImageIcon(ContactArtifactViewer.class.getResource(DEFAULT_IMAGE_PATH));
}
@@ -129,19 +131,15 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
// Reset the panel.
resetComponent();
- if (artifact == null) {
- return;
+ if (artifact != null) {
+ 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();
}
-
- 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.setLayout(this.m_gridBagLayout);
this.revalidate();
this.repaint();
@@ -163,6 +161,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
* 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 {
@@ -234,7 +233,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
/**
* Updates the contact image in the view.
*
- * @param contactPanelLayout Panel layout.
+ * @param contactPanelLayout Panel layout.
* @param contactPanelConstraints Layout constraints.
*
*/
@@ -245,8 +244,11 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
Insets savedInsets = contactPanelConstraints.insets;
contactPanelConstraints.gridy = 0;
contactPanelConstraints.gridx = 0;
- contactPanelConstraints.insets = new Insets(0, 0, 0, 0);
-
+ contactPanelConstraints.insets = new Insets(0, 0, ContentViewerDefaults.getLineSpacing(), 0);
+ int prevGridWidth = contactPanelConstraints.gridwidth;
+ contactPanelConstraints.gridwidth = 3;
+ contactPanelConstraints.anchor = GridBagConstraints.LINE_START;
+
javax.swing.JLabel contactImage = new javax.swing.JLabel();
contactImage.setIcon(getImageFromArtifact(contactArtifact));
contactImage.setText(Bundle.ContactArtifactViewer_contactImage_text());
@@ -256,13 +258,14 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
CommunicationArtifactViewerHelper.addLineEndGlue(this, contactPanelLayout, contactPanelConstraints);
contactPanelConstraints.gridy++;
+ contactPanelConstraints.gridwidth = prevGridWidth;
contactPanelConstraints.insets = savedInsets;
}
/**
* Updates the contact name in the view.
*
- * @param contactPanelLayout Panel layout.
+ * @param contactPanelLayout Panel layout.
* @param contactPanelConstraints Layout constraints.
*
*/
@@ -275,13 +278,13 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
if (StringUtils.isEmpty(bba.getValueString()) == false) {
contactName = bba.getDisplayString();
- CommunicationArtifactViewerHelper.addHeader(this, contactPanelLayout, contactPanelConstraints, contactName);
+ CommunicationArtifactViewerHelper.addHeader(this, contactPanelLayout, contactPanelConstraints, 0, contactName);
foundName = true;
break;
}
}
if (foundName == false) {
- CommunicationArtifactViewerHelper.addHeader(this, contactPanelLayout, contactPanelConstraints, Bundle.ContactArtifactViewer_contactname_unknown());
+ CommunicationArtifactViewerHelper.addHeader(this, contactPanelLayout, contactPanelConstraints, ContentViewerDefaults.getSectionSpacing(), Bundle.ContactArtifactViewer_contactname_unknown());
}
}
@@ -289,9 +292,9 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
* Updates the view by displaying the given list of attributes in the given
* section panel.
*
- * @param sectionAttributesList List of attributes to display.
- * @param sectionHeader Section name label.
- * @param contactPanelLayout Panel layout.
+ * @param sectionAttributesList List of attributes to display.
+ * @param sectionHeader Section name label.
+ * @param contactPanelLayout Panel layout.
* @param contactPanelConstraints Layout constraints.
*
*/
@@ -302,7 +305,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
return;
}
- CommunicationArtifactViewerHelper.addHeader(this, contactPanelLayout, contactPanelConstraints, sectionHeader);
+ CommunicationArtifactViewerHelper.addHeader(this, contactPanelLayout, contactPanelConstraints, ContentViewerDefaults.getSectionSpacing(), sectionHeader);
for (BlackboardAttribute bba : sectionAttributesList) {
CommunicationArtifactViewerHelper.addKey(this, contactPanelLayout, contactPanelConstraints, bba.getAttributeType().getDisplayName());
CommunicationArtifactViewerHelper.addValue(this, contactPanelLayout, contactPanelConstraints, bba.getDisplayString());
@@ -316,7 +319,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
"ContactArtifactViewer_heading_Source=Source",
"ContactArtifactViewer_label_datasource=Data Source",})
private void updateSource() {
- CommunicationArtifactViewerHelper.addHeader(this, this.m_gridBagLayout, m_constraints, Bundle.ContactArtifactViewer_heading_Source());
+ CommunicationArtifactViewerHelper.addHeader(this, this.m_gridBagLayout, m_constraints, ContentViewerDefaults.getSectionSpacing(), Bundle.ContactArtifactViewer_heading_Source());
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, m_constraints, Bundle.ContactArtifactViewer_label_datasource());
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, m_constraints, datasourceName);
}
@@ -335,7 +338,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
private void initiatePersonasSearch() {
// add a section header
- JLabel personaHeader = CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, m_constraints, Bundle.ContactArtifactViewer_persona_header());
+ JLabel personaHeader = CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, m_constraints, ContentViewerDefaults.getSectionSpacing(), Bundle.ContactArtifactViewer_persona_header());
m_constraints.gridy++;
@@ -346,8 +349,10 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
this.personaSearchStatusLabel = new javax.swing.JLabel();
personaSearchStatusLabel.setText(personaStatusLabelText);
-
+ personaSearchStatusLabel.setFont(ContentViewerDefaults.getMessageFont());
+
m_constraints.gridx = 0;
+ m_constraints.anchor = GridBagConstraints.LINE_START;
CommunicationArtifactViewerHelper.addComponent(this, m_gridBagLayout, m_constraints, personaSearchStatusLabel);
@@ -359,9 +364,8 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
personaHeader.setEnabled(false);
personaSearchStatusLabel.setEnabled(false);
- CommunicationArtifactViewerHelper.addBlankLine(this, m_gridBagLayout, m_constraints);
- m_constraints.gridy++;
- CommunicationArtifactViewerHelper.addMessageRow(this, m_gridBagLayout, m_constraints, Bundle.ContactArtifactViewer_cr_disabled_message());
+ Insets messageInsets = new Insets(ContentViewerDefaults.getSectionSpacing(), 0, ContentViewerDefaults.getLineSpacing(), 0);
+ CommunicationArtifactViewerHelper.addMessageRow(this, m_gridBagLayout, messageInsets, m_constraints, Bundle.ContactArtifactViewer_cr_disabled_message());
m_constraints.gridy++;
CommunicationArtifactViewerHelper.addPageEndGlue(this, m_gridBagLayout, this.m_constraints);
@@ -412,12 +416,12 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
/**
* Displays the given persona in the persona panel.
*
- * @param persona Persona to display.
- * @param matchNumber Number of matches.
+ * @param persona Persona to display.
+ * @param matchNumber Number of matches.
* @param missingAccountsList List of contact accounts this persona may be
- * missing.
- * @param gridBagLayout Layout to use.
- * @param constraints layout constraints.
+ * missing.
+ * @param gridBagLayout Layout to use.
+ * @param constraints layout constraints.
*
* @throws CentralRepoException
*/
@@ -435,12 +439,9 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
// save the original insets
Insets savedInsets = constraints.insets;
- // some label are indented 2x to appear indented w.r.t column above
- Insets extraIndentInsets = new java.awt.Insets(0, 2 * CommunicationArtifactViewerHelper.LEFT_INSET, 0, 0);
-
// Add a Match X label in col 0.
constraints.gridx = 0;
- javax.swing.JLabel matchNumberLabel = CommunicationArtifactViewerHelper.addKey(this, gridBagLayout, constraints, String.format("%s %d", Bundle.ContactArtifactViewer_persona_match_num(), matchNumber));
+ javax.swing.JLabel matchNumberLabel = CommunicationArtifactViewerHelper.addKey(this, gridBagLayout, constraints, String.format("%s %d", Bundle.ContactArtifactViewer_persona_match_num(), matchNumber).trim());
javax.swing.JLabel personaNameLabel = new javax.swing.JLabel();
javax.swing.JButton personaButton = new javax.swing.JButton();
@@ -461,6 +462,8 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
//constraints.gridwidth = 1; // TBD: this may not be needed if we use single panel
constraints.gridx++;
+ constraints.insets = new Insets(0, ContentViewerDefaults.getColumnSpacing(), ContentViewerDefaults.getLineSpacing(), 0);
+ constraints.anchor = GridBagConstraints.LINE_START;
personaNameLabel.setText(personaName);
gridBagLayout.setConstraints(personaNameLabel, constraints);
CommunicationArtifactViewerHelper.addComponent(this, gridBagLayout, constraints, personaNameLabel);
@@ -474,6 +477,8 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
// Shirnk the button height.
personaButton.setMargin(new Insets(0, 5, 0, 5));
+ constraints.insets = new Insets(0, ContentViewerDefaults.getColumnSpacing(), ContentViewerDefaults.getLineSpacing(), 0);
+ constraints.anchor = GridBagConstraints.LINE_START;
gridBagLayout.setConstraints(personaButton, constraints);
CommunicationArtifactViewerHelper.addComponent(this, gridBagLayout, constraints, personaButton);
CommunicationArtifactViewerHelper.addLineEndGlue(this, gridBagLayout, constraints);
@@ -488,7 +493,8 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
//constraints.insets = labelInsets;
javax.swing.JLabel accountsStatus = new javax.swing.JLabel(Bundle.ContactArtifactViewer_found_all_accounts_label());
- constraints.insets = extraIndentInsets;
+ constraints.insets = new Insets(0, ContentViewerDefaults.getColumnSpacing(), ContentViewerDefaults.getLineSpacing(), 0);
+ constraints.anchor = GridBagConstraints.LINE_START;
CommunicationArtifactViewerHelper.addComponent(this, gridBagLayout, constraints, accountsStatus);
constraints.insets = savedInsets;
@@ -501,7 +507,6 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
constraints.gridy++;
// this needs an extra indent
- constraints.insets = extraIndentInsets;
CommunicationArtifactViewerHelper.addKeyAtCol(this, gridBagLayout, constraints, Bundle.ContactArtifactViewer_missing_account_label(), 1);
constraints.insets = savedInsets;
@@ -544,12 +549,12 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
m_gridBagLayout = new GridBagLayout();
m_constraints = new GridBagConstraints();
- m_constraints.anchor = GridBagConstraints.FIRST_LINE_START;
+ m_constraints.anchor = GridBagConstraints.LINE_START;
m_constraints.gridy = 0;
m_constraints.gridx = 0;
m_constraints.weighty = 0.0;
m_constraints.weightx = 0.0; // keep components fixed horizontally.
- m_constraints.insets = new java.awt.Insets(0, CommunicationArtifactViewerHelper.LEFT_INSET, 0, 0);
+ m_constraints.insets = new java.awt.Insets(0, ContentViewerDefaults.getSectionIndent(), 0, 0);
m_constraints.fill = GridBagConstraints.NONE;
}
@@ -560,7 +565,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
* @param artifact
*
* @return Image from a TSK_CONTACT artifact or default image if none was
- * found or the artifact is not a TSK_CONTACT
+ * found or the artifact is not a TSK_CONTACT
*/
private ImageIcon getImageFromArtifact(BlackboardArtifact artifact) {
ImageIcon imageIcon = defaultImage;
@@ -610,7 +615,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
* Creates a persona searcher task.
*
* @param accountAttributesList List of attributes that may map to
- * accounts.
+ * accounts.
*/
ContactPersonaSearcherTask(BlackboardArtifact artifact) {
this.artifact = artifact;
@@ -620,48 +625,52 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
protected Map> doInBackground() throws Exception {
Map> uniquePersonas = new HashMap<>();
-
CommunicationsManager commManager = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager();
List contactAccountsList = commManager.getAccountsRelatedToArtifact(artifact);
for (Account account : contactAccountsList) {
- if (isCancelled()) {
- return new HashMap<>();
- }
+ try {
+ if (isCancelled()) {
+ return new HashMap<>();
+ }
- // make a list of all unique accounts for this contact
- if (!account.getAccountType().equals(Account.Type.DEVICE)) {
- Optional optCrAccountType = CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName());
- if (optCrAccountType.isPresent()) {
- CentralRepoAccount crAccount = CentralRepository.getInstance().getAccount(optCrAccountType.get(), account.getTypeSpecificID());
+ // make a list of all unique accounts for this contact
+ if (!account.getAccountType().equals(Account.Type.DEVICE)) {
+ Optional optCrAccountType = CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName());
+ if (optCrAccountType.isPresent()) {
+ CentralRepoAccount crAccount = CentralRepository.getInstance().getAccount(optCrAccountType.get(), account.getTypeSpecificID());
- if (crAccount != null && uniqueAccountsList.contains(crAccount) == false) {
- uniqueAccountsList.add(crAccount);
+ if (crAccount != null && uniqueAccountsList.contains(crAccount) == false) {
+ uniqueAccountsList.add(crAccount);
+ }
}
}
- }
-
- Collection personaAccounts = PersonaAccount.getPersonaAccountsForAccount(account);
- if (personaAccounts != null && !personaAccounts.isEmpty()) {
- // get personas for the account
- Collection personas
- = personaAccounts
- .stream()
- .map(PersonaAccount::getPersona)
- .collect(Collectors.toList());
- // make a list of unique personas, along with all their accounts
- for (Persona persona : personas) {
- if (uniquePersonas.containsKey(persona) == false) {
- Collection accounts = persona.getPersonaAccounts()
- .stream()
- .map(PersonaAccount::getAccount)
- .collect(Collectors.toList());
+ Collection personaAccounts = PersonaAccount.getPersonaAccountsForAccount(account);
+ if (personaAccounts != null && !personaAccounts.isEmpty()) {
+ // get personas for the account
+ Collection personas
+ = personaAccounts
+ .stream()
+ .map(PersonaAccount::getPersona)
+ .collect(Collectors.toList());
- ArrayList personaAccountsList = new ArrayList<>(accounts);
- uniquePersonas.put(persona, personaAccountsList);
+ // make a list of unique personas, along with all their accounts
+ for (Persona persona : personas) {
+ if (uniquePersonas.containsKey(persona) == false) {
+ Collection accounts = persona.getPersonaAccounts()
+ .stream()
+ .map(PersonaAccount::getAccount)
+ .collect(Collectors.toList());
+
+ ArrayList personaAccountsList = new ArrayList<>(accounts);
+ uniquePersonas.put(persona, personaAccountsList);
+ }
}
}
+ } catch (InvalidAccountIDException ex) {
+ // Do nothing, the account has an identifier that not an
+ // acceptable format for the cr.
}
}
@@ -709,7 +718,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
/**
* Constructor.
*
- * @param personaNameLabel Persona name label.
+ * @param personaNameLabel Persona name label.
* @param personaActionButton Persona action button.
*/
PersonaUIComponents(JLabel personaNameLabel, JButton personaActionButton) {
@@ -777,8 +786,8 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
for (CentralRepoAccount account : contactUniqueAccountsList) {
personaPanel.addAccount(account, Bundle.ContactArtifactViewer_persona_account_justification(), Persona.Confidence.HIGH);
}
-
- if(contactName != null && contactUniqueAccountsList.isEmpty()) {
+
+ if (contactName != null && contactUniqueAccountsList.isEmpty()) {
createPersonaDialog.setStartupPopupMessage(Bundle.ContactArtifactViewer_id_not_found_in_cr(contactName));
}
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultTableArtifactContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultTableArtifactContentViewer.java
index 0c90993a61..75882a39b0 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultTableArtifactContentViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/DefaultTableArtifactContentViewer.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2020 Basis Technology Corp.
+ * Copyright 2011-2021 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -41,7 +41,6 @@ import javax.swing.text.View;
import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
@@ -54,6 +53,7 @@ import com.google.gson.JsonArray;
import java.util.Locale;
import java.util.Map;
import javax.swing.SwingUtilities;
+import org.sleuthkit.autopsy.coreutils.TimeZoneUtils;
import org.sleuthkit.autopsy.discovery.ui.AbstractArtifactDetailsPanel;
//import org.sleuthkit.autopsy.contentviewers.Bundle;
@@ -343,7 +343,7 @@ public class DefaultTableArtifactContentViewer extends AbstractArtifactDetailsPa
// Use Autopsy date formatting settings, not TSK defaults
case DATETIME:
- value = epochTimeToString(attr.getValueLong());
+ value = TimeZoneUtils.getFormattedTime(attr.getValueLong());
break;
case JSON:
// Get the attribute's JSON value and convert to indented multiline display string
@@ -454,7 +454,7 @@ public class DefaultTableArtifactContentViewer extends AbstractArtifactDetailsPa
String attributeName = jsonKey;
String attributeValue;
if (attributeName.toUpperCase().contains("DATETIME")) {
- attributeValue = epochTimeToString(Long.parseLong(jsonElement.getAsString()));
+ attributeValue = TimeZoneUtils.getFormattedTime(Long.parseLong(jsonElement.getAsString()));
} else {
attributeValue = jsonElement.getAsString();
}
@@ -463,23 +463,6 @@ public class DefaultTableArtifactContentViewer extends AbstractArtifactDetailsPa
sb.append(NEW_LINE).append(String.format("%s%s = null", startIndent, jsonKey));
}
}
-
- /**
- * Converts epoch time to readable string.
- *
- * @param epochTime epoch time value to be converted to string.
- *
- * @return String with human readable time.
- */
- private String epochTimeToString(long epochTime) {
- String dateTimeString = "0000-00-00 00:00:00";
- if (null != content && 0 != epochTime) {
- dateFormatter.setTimeZone(ContentUtils.getTimeZone(content));
- dateTimeString = dateFormatter.format(new java.util.Date(epochTime * 1000));
- }
- return dateTimeString;
- }
-
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java
index 1b07ecd754..e81b6705c6 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java
@@ -1,7 +1,7 @@
/*
* Autopsy
*
- * Copyright 2020 Basis Technology Corp.
+ * Copyright 2020-2021 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,9 +18,9 @@
*/
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
+import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
-import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
@@ -30,25 +30,30 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
+import javax.swing.border.EmptyBorder;
import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
+import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerDefaults;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
-import org.sleuthkit.autopsy.datamodel.ContentUtils;
+import org.sleuthkit.autopsy.coreutils.TimeZoneUtils;
import org.sleuthkit.autopsy.discovery.ui.AbstractArtifactDetailsPanel;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
-import org.sleuthkit.datamodel.TimeUtilities;
import org.sleuthkit.datamodel.TskCoreException;
/**
@@ -56,13 +61,18 @@ import org.sleuthkit.datamodel.TskCoreException;
*/
@ServiceProvider(service = ArtifactContentViewer.class)
public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel implements ArtifactContentViewer {
-
+
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(GeneralPurposeArtifactViewer.class.getName());
// Number of columns in the gridbag layout.
private final static int MAX_COLS = 4;
- private final static Insets ROW_INSETS = new java.awt.Insets(0, 12, 0, 0);
- private final static Insets HEADER_INSETS = new java.awt.Insets(0, 0, 0, 0);
+ private final static Insets ZERO_INSETS = new java.awt.Insets(0, 0, 0, 0);
+
+ private final static Insets FIRST_HEADER_INSETS = ZERO_INSETS;
+ private final static Insets HEADER_INSETS = new Insets(ContentViewerDefaults.getSectionSpacing(), 0, ContentViewerDefaults.getLineSpacing(), 0);
+ private final static Insets VALUE_COLUMN_INSETS = new Insets(0, ContentViewerDefaults.getColumnSpacing(), ContentViewerDefaults.getLineSpacing(), 0);
+ private final static Insets KEY_COLUMN_INSETS = new Insets(0, ContentViewerDefaults.getSectionIndent(), ContentViewerDefaults.getLineSpacing(), 0);
+
private final static double GLUE_WEIGHT_X = 1.0;
private final static double TEXT_WEIGHT_X = 0.0;
private final static int LABEL_COLUMN = 0;
@@ -92,6 +102,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i
initComponents();
gridBagConstraints.anchor = GridBagConstraints.FIRST_LINE_START;
detailsPanel.setLayout(gridBagLayout);
+ detailsPanel.setBorder(new EmptyBorder(ContentViewerDefaults.getPanelInsets()));
}
/**
@@ -181,7 +192,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i
gridBagConstraints.weighty = 0.0;
gridBagConstraints.weightx = TEXT_WEIGHT_X; // keep components fixed horizontally.
gridBagConstraints.fill = GridBagConstraints.NONE;
- gridBagConstraints.insets = ROW_INSETS;
+ gridBagConstraints.insets = ZERO_INSETS;
}
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@@ -263,9 +274,9 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i
for (BlackboardAttribute bba : attrList) {
if (bba.getAttributeType().getTypeName().startsWith("TSK_DATETIME")) {
if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID()) {
- addNameValueRow(Bundle.GeneralPurposeArtifactViewer_dates_time(), TimeUtilities.epochToTime(bba.getValueLong(), ContentUtils.getTimeZone(artifact)));
+ addNameValueRow(Bundle.GeneralPurposeArtifactViewer_dates_time(), TimeZoneUtils.getFormattedTime(bba.getValueLong()));
} else {
- addNameValueRow(bba.getAttributeType().getDisplayName(), TimeUtilities.epochToTime(bba.getValueLong(), ContentUtils.getTimeZone(artifact)));
+ addNameValueRow(bba.getAttributeType().getDisplayName(), TimeZoneUtils.getFormattedTime(bba.getValueLong()));
}
} else if (bba.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT.getTypeID() && artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID()) {
addNameValueRow(Bundle.GeneralPurposeArtifactViewer_term_label(), bba.getDisplayString());
@@ -293,7 +304,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i
for (int key : attributeMap.keySet()) {
for (BlackboardAttribute bba : attributeMap.get(key)) {
if (bba.getAttributeType().getTypeName().startsWith("TSK_DATETIME")) {
- addNameValueRow(bba.getAttributeType().getDisplayName(), TimeUtilities.epochToTime(bba.getValueLong(), ContentUtils.getTimeZone(artifact)));
+ addNameValueRow(bba.getAttributeType().getDisplayName(), TimeZoneUtils.getFormattedTime(bba.getValueLong()));
} else {
addNameValueRow(bba.getAttributeType().getDisplayName(), bba.getDisplayString());
}
@@ -387,29 +398,26 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i
headingLabel.setEditable(false);
// add a blank line before the start of new section, unless it's
// the first section
- if (gridBagConstraints.gridy != 0) {
- gridBagConstraints.gridy++;
- // add to panel
- addToPanel(new javax.swing.JLabel(" "));
- addLineEndGlue();
- headingLabel.setFocusable(false);
- }
+ gridBagConstraints.insets = (gridBagConstraints.gridy == 0)
+ ? FIRST_HEADER_INSETS
+ : HEADER_INSETS;
+
gridBagConstraints.gridy++;
gridBagConstraints.gridx = LABEL_COLUMN;;
// let the header span all of the row
gridBagConstraints.gridwidth = MAX_COLS;
- gridBagConstraints.insets = HEADER_INSETS;
// set text
headingLabel.setText(headerString);
// make it large and bold
- headingLabel.setFont(headingLabel.getFont().deriveFont(Font.BOLD, headingLabel.getFont().getSize() + 2));
+ headingLabel.setFont(ContentViewerDefaults.getHeaderFont());
+ headingLabel.setMargin(ZERO_INSETS);
// add to panel
addToPanel(headingLabel);
// reset constraints to normal
gridBagConstraints.gridwidth = LABEL_WIDTH;
// add line end glue
addLineEndGlue();
- gridBagConstraints.insets = ROW_INSETS;
+ gridBagConstraints.insets = ZERO_INSETS;
return headingLabel;
}
@@ -466,6 +474,7 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i
javax.swing.JLabel keyLabel = new javax.swing.JLabel();
keyLabel.setFocusable(false);
gridBagConstraints.gridy++;
+ gridBagConstraints.insets = KEY_COLUMN_INSETS;
gridBagConstraints.gridx = LABEL_COLUMN;
gridBagConstraints.gridwidth = LABEL_WIDTH;
// set text
@@ -493,7 +502,9 @@ public class GeneralPurposeArtifactViewer extends AbstractArtifactDetailsPanel i
valueField.setFocusable(false);
valueField.setEditable(false);
valueField.setOpaque(false);
+ valueField.setMargin(ZERO_INSETS);
gridBagConstraints.gridx = VALUE_COLUMN;
+ gridBagConstraints.insets = VALUE_COLUMN_INSETS;
GridBagConstraints cloneConstraints = (GridBagConstraints) gridBagConstraints.clone();
// let the value span 2 cols
cloneConstraints.gridwidth = VALUE_WIDTH;
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/MessageAccountPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/MessageAccountPanel.java
index fbf1ada856..c2847223e6 100755
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/MessageAccountPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/MessageAccountPanel.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2020 Basis Technology Corp.
+ * Copyright 2020-2021 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
import java.awt.Dimension;
+import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
@@ -44,6 +45,7 @@ import javax.swing.JTextPane;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
+import javax.swing.border.EmptyBorder;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
@@ -56,6 +58,7 @@ 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.contentviewers.layout.ContentViewerDefaults;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.guiutils.ContactCache;
import org.sleuthkit.datamodel.Account;
@@ -77,6 +80,15 @@ final class MessageAccountPanel extends JPanel {
private AccountFetcher currentFetcher = null;
+
+
+ /**
+ * Main constructor.
+ */
+ MessageAccountPanel() {
+ this.setBorder(new EmptyBorder(ContentViewerDefaults.getPanelInsets()));
+ }
+
/**
* Set the new artifact for the panel.
*
@@ -170,9 +182,7 @@ final class MessageAccountPanel extends JPanel {
layout.setHorizontalGroup(
layout.createParallelGroup(Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
- .addContainerGap()
- .addGroup(getMainHorizontalGroup(layout, dataList))
- .addContainerGap(158, Short.MAX_VALUE)));
+ .addGroup(getMainHorizontalGroup(layout, dataList))));
layout.setVerticalGroup(getMainVerticalGroup(layout, dataList));
setLayout(layout);
@@ -186,6 +196,7 @@ final class MessageAccountPanel extends JPanel {
messageLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
messageLabel.setText(Bundle.MessageAccountPanel_no_matches());
+ messageLabel.setFont(ContentViewerDefaults.getMessageFont());
messageLabel.setEnabled(false);
messagePanel.add(messageLabel, java.awt.BorderLayout.CENTER);
@@ -224,14 +235,12 @@ final class MessageAccountPanel extends JPanel {
private ParallelGroup getMainVerticalGroup(GroupLayout layout, List data) {
SequentialGroup group = layout.createSequentialGroup();
for (AccountContainer o : data) {
- group.addGap(5)
- .addComponent(o.getAccountLabel())
+ group.addComponent(o.getAccountLabel())
.addGroup(o.getContactLineVerticalGroup(layout))
.addGroup(o.getPersonLineVerticalGroup(layout));
+ group.addGap(ContentViewerDefaults.getSectionSpacing());
}
- group.addContainerGap(83, Short.MAX_VALUE);
-
return layout.createParallelGroup().addGroup(group);
}
@@ -259,12 +268,11 @@ final class MessageAccountPanel extends JPanel {
private SequentialGroup getPersonaHorizontalGroup(GroupLayout layout, List data) {
SequentialGroup group = layout.createSequentialGroup();
ParallelGroup pgroup = layout.createParallelGroup(Alignment.LEADING);
- group.addGap(10);
for (AccountContainer o : data) {
pgroup.addGroup(o.getPersonaSequentialGroup(layout));
pgroup.addGroup(o.getContactSequentialGroup(layout));
}
- group.addGap(10)
+ group.addGap(ContentViewerDefaults.getSectionIndent())
.addGroup(pgroup)
.addPreferredGap(ComponentPlacement.RELATED)
.addGroup(getButtonGroup(layout, data));
@@ -343,7 +351,9 @@ final class MessageAccountPanel extends JPanel {
button = new JButton();
button.addActionListener(new PersonaButtonListener(this));
+ accountLabel.setMargin(new Insets(0, 0, 0, 0));
accountLabel.setText(account.getTypeSpecificID());
+ accountLabel.setFont(ContentViewerDefaults.getHeaderFont());
contactDisplayName.setText(contactName);
personaDisplayName.setText(persona != null ? persona.getName() : Bundle.MessageAccountPanel_unknown_label());
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.form
index c57a0990a8..9b2e224b22 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.form
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.form
@@ -2,11 +2,17 @@