diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java index deaa0d1ca8..34f26eb1ac 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java @@ -130,8 +130,7 @@ public class FileManager implements Closeable { * @throws TskCoreException */ public List findFilesExactName(long parentId, String name) throws TskCoreException{ - String whereClause = "name = '%s'"; - return caseDb.findAllFilesInFolderWhere(parentId, String.format(whereClause, name)); + return caseDb.getFileManager().findFilesExactName(parentId, name); } /** diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InstanceCountNode.java index 5be8f8bae2..628bec9291 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InstanceCountNode.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2018-2019 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -60,7 +60,7 @@ public final class InstanceCountNode extends DisplayableItemNode { "InstanceCountNode.displayName=Exists in %s data sources (%s)" }) public InstanceCountNode(int instanceCount, CommonAttributeValueList attributeValues, CorrelationAttributeInstance.Type type) { - super(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList(), type), false)); + super(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList(), type), true)); this.type = type; this.instanceCount = instanceCount; this.attributeValues = attributeValues; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java index 5d109438c3..aad1fe79a8 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2018-2020 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,10 +21,7 @@ package org.sleuthkit.autopsy.contentviewers; import java.awt.Component; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import javax.swing.JLabel; import javax.swing.SwingWorker; -import javax.swing.text.EditorKit; -import javax.swing.text.html.HTMLEditorKit; import static org.openide.util.NbBundle.Messages; import org.openide.nodes.Node; @@ -36,6 +33,7 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.contentviewers.application.Annotations; import org.sleuthkit.autopsy.coreutils.Logger; import org.jsoup.nodes.Document; +import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerHtmlStyles; /** * Annotations view of file contents. @@ -48,33 +46,6 @@ import org.jsoup.nodes.Document; "AnnotationsContentViewer.onEmpty=No annotations were found for this particular item." }) public class AnnotationsContentViewer extends javax.swing.JPanel implements DataContentViewer { - - private static final int DEFAULT_FONT_SIZE = new JLabel().getFont().getSize(); - - // how big the subheader should be - private static final int SUBHEADER_FONT_SIZE = DEFAULT_FONT_SIZE * 12 / 11; - - // how big the header should be - private static final int HEADER_FONT_SIZE = DEFAULT_FONT_SIZE * 14 / 11; - - // the subsection indent - private static final int DEFAULT_SUBSECTION_LEFT_PAD = DEFAULT_FONT_SIZE; - - // spacing occurring after an item - private static final int DEFAULT_SECTION_SPACING = DEFAULT_FONT_SIZE * 2; - private static final int DEFAULT_SUBSECTION_SPACING = DEFAULT_FONT_SIZE / 2; - private static final int CELL_SPACING = DEFAULT_FONT_SIZE / 2; - - // additional styling for components - private static final String STYLE_SHEET_RULE - = String.format(" .%s { font-size: %dpx;font-style:italic; margin: 0px; padding: 0px; } ", Annotations.MESSAGE_CLASSNAME, DEFAULT_FONT_SIZE) - + String.format(" .%s {font-size:%dpx;font-weight:bold; margin: 0px; margin-top: %dpx; padding: 0px; } ", - Annotations.SUBHEADER_CLASSNAME, SUBHEADER_FONT_SIZE, DEFAULT_SUBSECTION_SPACING) - + String.format(" .%s { font-size:%dpx;font-weight:bold; margin: 0px; padding: 0px; } ", Annotations.HEADER_CLASSNAME, HEADER_FONT_SIZE) - + String.format(" td { vertical-align: top; font-size:%dpx; text-align: left; margin: 0px; padding: 0px %dpx 0px 0px;} ", DEFAULT_FONT_SIZE, CELL_SPACING) - + String.format(" th { vertical-align: top; text-align: left; margin: 0px; padding: 0px %dpx 0px 0px} ", DEFAULT_FONT_SIZE, CELL_SPACING) - + String.format(" .%s { margin: %dpx 0px; padding-left: %dpx; } ", Annotations.SUBSECTION_CLASSNAME, DEFAULT_SUBSECTION_SPACING, DEFAULT_SUBSECTION_LEFT_PAD) - + String.format(" .%s { margin-bottom: %dpx; } ", Annotations.SECTION_CLASSNAME, DEFAULT_SECTION_SPACING); private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(AnnotationsContentViewer.class.getName()); @@ -86,13 +57,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data */ public AnnotationsContentViewer() { initComponents(); - Utilities.configureTextPaneAsHtml(textPanel); - // get html editor kit and apply additional style rules - EditorKit editorKit = textPanel.getEditorKit(); - if (editorKit instanceof HTMLEditorKit) { - HTMLEditorKit htmlKit = (HTMLEditorKit) editorKit; - htmlKit.getStyleSheet().addRule(STYLE_SHEET_RULE); - } + ContentViewerHtmlStyles.setupHtmlJTextPane(textPanel); } @Override @@ -223,7 +188,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data if(doc != null) { return doc.html(); } else { - return Bundle.AnnotationsContentViewer_onEmpty(); + return "" + Bundle.AnnotationsContentViewer_onEmpty() + ""; } } @@ -235,6 +200,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data try { String text = get(); + ContentViewerHtmlStyles.setStyles(textPanel); textPanel.setText(text); textPanel.setCaretPosition(0); } catch (InterruptedException | ExecutionException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.form b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.form index b175b6b512..8e6f123911 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.form @@ -41,8 +41,6 @@ - - diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java index f37cccb028..bd3671e44b 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java @@ -20,16 +20,22 @@ 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.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.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import org.sleuthkit.datamodel.AbstractFile; @@ -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(key) + )); + } + + 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 } @@ -240,11 +277,9 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer { if (node != null && !node.getLookup().lookupAll(DataArtifact.class).isEmpty()) { return Bundle.Metadata_dataArtifactTitle(); } else { - return NbBundle.getMessage(this.getClass(), "Metadata.title"); + return NbBundle.getMessage(this.getClass(), "Metadata.title"); } } - - @Override public String getToolTip() { @@ -299,6 +334,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) { @@ -357,30 +393,35 @@ 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); + startTable(sb); - /* + List istatStrings = Collections.emptyList(); + try { + istatStrings = fsFile.getMetaDataText(); + } catch (TskCoreException ex) { + istatStrings = Arrays.asList(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.exceptionNotice.text") + ex.getLocalizedMessage()); + } + + 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")); - break; - } + */ + if (sb.length() > 50000) { + addMonospacedRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.nodeText.truncated")); + break; } - sb.append("
\n"); //NON-NLS } - } catch (TskCoreException ex) { - sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.exceptionNotice.text")).append(ex.getLocalizedMessage()); + + endTable(sb); } + } else { try { addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.name"), image.getUniquePath()); @@ -419,20 +460,18 @@ 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 index 22fbe9e8a1..98fb50c89a 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.form @@ -42,9 +42,6 @@ - - - diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.java index bf3c45d0f3..3a5a670f49 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/analysisresults/AnalysisResultsContentPanel.java @@ -18,12 +18,9 @@ */ package org.sleuthkit.autopsy.contentviewers.analysisresults; -import java.awt.Color; import java.text.MessageFormat; import java.util.List; import java.util.Optional; -import javax.swing.JLabel; -import javax.swing.text.html.HTMLEditorKit; import org.apache.commons.lang3.tuple.Pair; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -31,6 +28,8 @@ import org.jsoup.nodes.Element; import org.openide.util.NbBundle; 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.Score; @@ -38,59 +37,21 @@ 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 = ""; - private static final String DEFAULT_FONT_FAMILY = new JLabel().getFont().getFamily(); - private static final int DEFAULT_FONT_SIZE = new JLabel().getFont().getSize(); - private static final Color DEFAULT_BACKGROUND = new JLabel().getBackground(); - - // html stylesheet classnames for components - private static final String ANALYSIS_RESULTS_CLASS_PREFIX = "analysisResult_"; - private static final String SPACED_SECTION_CLASSNAME = ANALYSIS_RESULTS_CLASS_PREFIX + "spacedSection"; - private static final String SUBSECTION_CLASSNAME = ANALYSIS_RESULTS_CLASS_PREFIX + "subsection"; - private static final String HEADER_CLASSNAME = ANALYSIS_RESULTS_CLASS_PREFIX + "header"; - public static final String MESSAGE_CLASSNAME = ANALYSIS_RESULTS_CLASS_PREFIX + "message"; - public static final String TD_CLASSNAME = ANALYSIS_RESULTS_CLASS_PREFIX + "td"; - // 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_"; - // how big the header should be - private static final int HEADER_FONT_SIZE = DEFAULT_FONT_SIZE + 2; - - // spacing occurring after an item - private static final int DEFAULT_SECTION_SPACING = DEFAULT_FONT_SIZE / 2; - private static final int CELL_SPACING = DEFAULT_FONT_SIZE / 2; - - // the subsection indent - private static final int DEFAULT_SUBSECTION_LEFT_PAD = DEFAULT_FONT_SIZE; - - // additional styling for components - private static final String STYLE_SHEET_RULE - = String.format(" .%s { font-size: %dpt;font-style:italic; margin: 0px; padding: 0px; } ", MESSAGE_CLASSNAME, DEFAULT_FONT_SIZE) - + String.format(" .%s { font-family: %s; font-size: %dpt; font-weight: bold; margin: 0px; padding: 0px; } ", - HEADER_CLASSNAME, DEFAULT_FONT_FAMILY, HEADER_FONT_SIZE) - + String.format(" .%s { vertical-align: top; font-family: %s; font-size: %dpt; text-align: left; margin: 0pt; padding: 0px %dpt 0px 0px;} ", - TD_CLASSNAME, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, CELL_SPACING) - + String.format(" .%s { margin-top: %dpt; } ", SPACED_SECTION_CLASSNAME, DEFAULT_SECTION_SPACING) - + String.format(" .%s { padding-left: %dpt; }", SUBSECTION_CLASSNAME, DEFAULT_SUBSECTION_LEFT_PAD); - - - /** * Creates new form AnalysisResultsContentViewer */ public AnalysisResultsContentPanel() { initComponents(); - - textPanel.setContentType("text/html;charset=UTF-8"); //NON-NLS - HTMLEditorKit kit = new HTMLEditorKit(); - textPanel.setEditorKit(kit); - kit.getStyleSheet().addRule(STYLE_SHEET_RULE); + ContentViewerHtmlStyles.setupHtmlJTextPane(textPanel); } /** @@ -99,10 +60,11 @@ public class AnalysisResultsContentPanel extends javax.swing.JPanel { * @param message The message to be displayed. */ void showMessage(String message) { + ContentViewerHtmlStyles.setStyles(textPanel); textPanel.setText("" - + MessageFormat.format("

{1}

", - MESSAGE_CLASSNAME, - message == null ? "" : message) + + MessageFormat.format("

{1}

", + ContentViewerHtmlStyles.getMessageClassName(), + message == null ? "" : EscapeUtil.escapeHtml(message)) + ""); } @@ -137,12 +99,16 @@ public class AnalysisResultsContentPanel extends javax.swing.JPanel { List displayAttributes = nodeResults.getAnalysisResults(); for (int idx = 0; idx < displayAttributes.size(); idx++) { AnalysisResultsViewModel.ResultDisplayAttributes resultAttrs = displayAttributes.get(idx); - appendResult(body, idx, resultAttrs); + Element sectionDiv = appendResult(body, idx, resultAttrs); + if (idx > 0 || aggregateScore.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()) { @@ -155,48 +121,57 @@ public class AnalysisResultsContentPanel extends javax.swing.JPanel { /** * 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. + * @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 void appendResult(Element parent, int index, AnalysisResultsViewModel.ResultDisplayAttributes attrs) { + 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"); - table.attr("class", SUBSECTION_CLASSNAME); - + 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() + ":"; - row.appendElement("td") + Element keyTd = row.appendElement("td") + .attr("class", ContentViewerHtmlStyles.getTextClassName()); + + keyTd.appendElement("span") .text(keyString) - .attr("class", TD_CLASSNAME); + .attr("class", ContentViewerHtmlStyles.getKeyColumnClassName()); String valueString = keyVal.getValue() == null ? "" : keyVal.getValue(); row.appendElement("td") .text(valueString) - .attr("class", TD_CLASSNAME); + .attr("class", ContentViewerHtmlStyles.getTextClassName()); } + + return sectionDiv; } /** @@ -210,21 +185,25 @@ public class AnalysisResultsContentPanel extends javax.swing.JPanel { */ 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()) { - Element anchorEl = sectionDiv.appendElement("a"); + anchorEl = sectionDiv.appendElement("a"); anchorEl.attr("name", anchorId.get()); + anchorEl.attr("style", "padding: 0px; margin: 0px; display: inline-block;"); } - // set the class for the section - sectionDiv.attr("class", SPACED_SECTION_CLASSNAME); - // append the header - Element header = sectionDiv.appendElement("h1"); - header.text(headerText); - header.attr("class", HEADER_CLASSNAME); + 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; } @@ -244,7 +223,6 @@ public class AnalysisResultsContentPanel extends javax.swing.JPanel { setPreferredSize(new java.awt.Dimension(100, 58)); textPanel.setEditable(false); - textPanel.setBackground(DEFAULT_BACKGROUND); textPanel.setName(""); // NOI18N textPanel.setPreferredSize(new java.awt.Dimension(600, 52)); scrollPane.setViewportView(textPanel); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/application/Annotations.java b/Core/src/org/sleuthkit/autopsy/contentviewers/application/Annotations.java index feec2703dc..9651ddc518 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,18 @@ 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 +232,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 +453,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 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 +508,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()); } } @@ -551,6 +559,7 @@ public class Annotations { Element row = rowParent.appendElement("tr"); for (int i = 0; i < columnNumber; i++) { Element cell = row.appendElement(cellType); + cell.attr("class", ContentViewerHtmlStyles.getTextClassName()); if (data != null && i < data.size()) { cell.text(StringUtils.isEmpty(data.get(i)) ? "" : data.get(i)); } @@ -568,10 +577,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 +593,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 +612,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/artifactviewers/CallLogArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/CallLogArtifactViewer.java index 8d27c6c5be..553c139c55 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())); } /** @@ -114,6 +118,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac currentAccountFetcher = null; } } + // repaint this.revalidate(); } @@ -309,7 +314,7 @@ 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 @@ -384,7 +389,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 +419,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 +437,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 +449,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 +509,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..b9ebee5c67 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; @@ -107,7 +108,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)); } @@ -245,8 +246,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,6 +260,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac CommunicationArtifactViewerHelper.addLineEndGlue(this, contactPanelLayout, contactPanelConstraints); contactPanelConstraints.gridy++; + contactPanelConstraints.gridwidth = prevGridWidth; contactPanelConstraints.insets = savedInsets; } @@ -275,13 +280,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()); } } @@ -302,7 +307,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 +321,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 +340,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 +351,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 +366,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); @@ -435,12 +441,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 +464,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 +479,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 +495,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 +509,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 +551,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; } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java index d8150a91a2..e81b6705c6 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/GeneralPurposeArtifactViewer.java @@ -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,18 +30,24 @@ 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.coreutils.TimeZoneUtils; @@ -55,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; @@ -91,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())); } /** @@ -180,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) @@ -386,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; } @@ -465,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 @@ -492,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 @@
- - + + + + + + + + - + @@ -24,15 +30,13 @@ - - + - - + - + @@ -41,14 +45,13 @@ - - + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.java index 02bd3f2ae6..0f7d2855bf 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.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.contextviewer; import java.util.ArrayList; import java.util.List; import org.sleuthkit.autopsy.contentviewers.contextviewer.ContextViewer.DateTimePanel; +import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerDefaults; import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; @@ -75,8 +76,10 @@ public final class ContextSourcePanel extends javax.swing.JPanel implements Date jSourceNameLabel = new javax.swing.JLabel(); jSourceTextLabel = new javax.swing.JLabel(); - setBackground(new java.awt.Color(255, 255, 255)); - setPreferredSize(new java.awt.Dimension(495, 75)); + setBackground(ContentViewerDefaults.getPanelBackground()); + setMaximumSize(new java.awt.Dimension(495, 55)); + setMinimumSize(new java.awt.Dimension(300, 55)); + setPreferredSize(new java.awt.Dimension(495, 55)); org.openide.awt.Mnemonics.setLocalizedText(jSourceGoToResultButton, org.openide.util.NbBundle.getMessage(ContextSourcePanel.class, "ContextSourcePanel.jSourceGoToResultButton.text")); // NOI18N jSourceGoToResultButton.addActionListener(new java.awt.event.ActionListener() { @@ -94,26 +97,23 @@ public final class ContextSourcePanel extends javax.swing.JPanel implements Date layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(50, 50, 50) .addComponent(jSourceNameLabel) .addGap(36, 36, 36) - .addComponent(jSourceTextLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(260, 260, 260)) + .addComponent(jSourceTextLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 360, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() - .addGap(90, 90, 90) + .addGap(40, 40, 40) .addComponent(jSourceGoToResultButton) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(2, 2, 2) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jSourceNameLabel) .addComponent(jSourceTextLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jSourceGoToResultButton) - .addGap(0, 0, 0)) + .addGap(0, 11, Short.MAX_VALUE)) ); }// //GEN-END:initComponents diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.form index a3e2a90f84..2faf5bf779 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.form @@ -2,11 +2,17 @@ - - + + + + + + + + - + @@ -24,30 +30,28 @@ - - + - - + - + - + - + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.java index 4a96dfcffd..c3c74da190 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.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"); @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.contentviewers.contextviewer; import java.util.ArrayList; import java.util.List; +import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerDefaults; import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; @@ -74,8 +75,10 @@ public final class ContextUsagePanel extends javax.swing.JPanel implements Conte jUsageNameLabel = new javax.swing.JLabel(); jUsageTextLabel = new javax.swing.JLabel(); - setBackground(new java.awt.Color(255, 255, 255)); - setPreferredSize(new java.awt.Dimension(495, 75)); + setBackground(ContentViewerDefaults.getPanelBackground()); + setMaximumSize(new java.awt.Dimension(32767, 55)); + setMinimumSize(new java.awt.Dimension(300, 55)); + setPreferredSize(new java.awt.Dimension(495, 55)); org.openide.awt.Mnemonics.setLocalizedText(jUsageGoToResultButton, org.openide.util.NbBundle.getMessage(ContextUsagePanel.class, "ContextUsagePanel.jUsageGoToResultButton.text")); // NOI18N jUsageGoToResultButton.addActionListener(new java.awt.event.ActionListener() { @@ -93,25 +96,23 @@ public final class ContextUsagePanel extends javax.swing.JPanel implements Conte layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(50, 50, 50) .addComponent(jUsageNameLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jUsageTextLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 240, Short.MAX_VALUE) - .addGap(36, 36, 36)) + .addComponent(jUsageTextLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 420, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() - .addGap(90, 90, 90) + .addGap(40, 40, 40) .addComponent(jUsageGoToResultButton) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(275, javax.swing.GroupLayout.PREFERRED_SIZE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(2, 2, 2) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jUsageTextLabel) .addComponent(jUsageNameLabel, javax.swing.GroupLayout.Alignment.TRAILING)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jUsageGoToResultButton)) + .addComponent(jUsageGoToResultButton) + .addGap(0, 11, Short.MAX_VALUE)) ); }// //GEN-END:initComponents diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.form index 01c484ba9e..47c452eccb 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.form @@ -4,38 +4,19 @@ - - + + - - - - - - - - - - - - - - - - - - - + + - - - - + + @@ -50,38 +31,19 @@ - - + + - - - - - - - - - - - - - - - - - - - + + - - - - + + @@ -95,31 +57,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -127,6 +67,9 @@ + + + @@ -137,11 +80,8 @@ - - - - + @@ -171,8 +111,8 @@ - - + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java index bfa0cfc3e9..eae47348d2 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-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.contextviewer; import java.awt.Component; +import java.awt.Insets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -28,12 +29,14 @@ import java.util.Map; import java.util.logging.Level; import javax.swing.BoxLayout; import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED; +import javax.swing.border.EmptyBorder; import org.apache.commons.lang.StringUtils; 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.contentviewers.layout.ContentViewerDefaults; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; @@ -55,6 +58,10 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte private static final Logger logger = Logger.getLogger(ContextViewer.class.getName()); private static final int ARTIFACT_STR_MAX_LEN = 1024; private static final int ATTRIBUTE_STR_MAX_LEN = 200; + + private final static Insets FIRST_HEADER_INSETS = new Insets(0, 0, 0, 0); + private final static Insets HEADER_INSETS = new Insets(ContentViewerDefaults.getSectionSpacing(), 0, ContentViewerDefaults.getLineSpacing(), 0); + private final static Insets DATA_ROW_INSETS = new Insets(0, ContentViewerDefaults.getSectionIndent(), ContentViewerDefaults.getLineSpacing(), 0); // defines a list of artifacts that provide context for a file private static final List CONTEXT_ARTIFACTS = new ArrayList<>(); @@ -91,75 +98,30 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte javax.swing.JLabel jUnknownLabel = new javax.swing.JLabel(); jScrollPane = new javax.swing.JScrollPane(); - jSourcePanel.setBackground(javax.swing.UIManager.getDefaults().getColor("window")); + jSourcePanel.setBorder(new EmptyBorder(FIRST_HEADER_INSETS)); + jSourcePanel.setLayout(new javax.swing.BoxLayout(jSourcePanel, javax.swing.BoxLayout.PAGE_AXIS)); - jSourceLabel.setFont(jSourceLabel.getFont().deriveFont(jSourceLabel.getFont().getStyle() | java.awt.Font.BOLD, jSourceLabel.getFont().getSize()+1)); + jSourceLabel.setFont(ContentViewerDefaults.getHeaderFont()); org.openide.awt.Mnemonics.setLocalizedText(jSourceLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jSourceLabel.text")); // NOI18N + jSourcePanel.add(jSourceLabel); - javax.swing.GroupLayout jSourcePanelLayout = new javax.swing.GroupLayout(jSourcePanel); - jSourcePanel.setLayout(jSourcePanelLayout); - jSourcePanelLayout.setHorizontalGroup( - jSourcePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jSourcePanelLayout.createSequentialGroup() - .addGap(40, 40, 40) - .addComponent(jSourceLabel) - .addContainerGap(304, Short.MAX_VALUE)) + jUsagePanel.setBorder(new EmptyBorder(HEADER_INSETS)); + jUsagePanel.setLayout(new javax.swing.BoxLayout(jUsagePanel, javax.swing.BoxLayout.PAGE_AXIS)); + + jUsageLabel.setFont(ContentViewerDefaults.getHeaderFont() ); - jSourcePanelLayout.setVerticalGroup( - jSourcePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jSourcePanelLayout.createSequentialGroup() - .addGap(5, 5, 5) - .addComponent(jSourceLabel) - .addGap(2, 2, 2)) - ); - - jUsagePanel.setBackground(javax.swing.UIManager.getDefaults().getColor("window")); - - jUsageLabel.setFont(jUsageLabel.getFont().deriveFont(jUsageLabel.getFont().getStyle() | java.awt.Font.BOLD, jUsageLabel.getFont().getSize()+1)); org.openide.awt.Mnemonics.setLocalizedText(jUsageLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jUsageLabel.text")); // NOI18N + jUsagePanel.add(jUsageLabel); - javax.swing.GroupLayout jUsagePanelLayout = new javax.swing.GroupLayout(jUsagePanel); - jUsagePanel.setLayout(jUsagePanelLayout); - jUsagePanelLayout.setHorizontalGroup( - jUsagePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jUsagePanelLayout.createSequentialGroup() - .addGap(40, 40, 40) - .addComponent(jUsageLabel) - .addContainerGap(298, Short.MAX_VALUE)) - ); - jUsagePanelLayout.setVerticalGroup( - jUsagePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jUsagePanelLayout.createSequentialGroup() - .addGap(2, 2, 2) - .addComponent(jUsageLabel) - .addGap(2, 2, 2)) - ); - - jUnknownPanel.setBackground(new java.awt.Color(255, 255, 255)); + jUnknownPanel.setLayout(new javax.swing.BoxLayout(jUnknownPanel, javax.swing.BoxLayout.PAGE_AXIS)); org.openide.awt.Mnemonics.setLocalizedText(jUnknownLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jUnknownLabel.text")); // NOI18N + jUnknownLabel.setBorder(new EmptyBorder(DATA_ROW_INSETS)); + jUnknownPanel.add(jUnknownLabel); - javax.swing.GroupLayout jUnknownPanelLayout = new javax.swing.GroupLayout(jUnknownPanel); - jUnknownPanel.setLayout(jUnknownPanelLayout); - jUnknownPanelLayout.setHorizontalGroup( - jUnknownPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jUnknownPanelLayout.createSequentialGroup() - .addGap(50, 50, 50) - .addComponent(jUnknownLabel) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - ); - jUnknownPanelLayout.setVerticalGroup( - jUnknownPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jUnknownPanelLayout.createSequentialGroup() - .addGap(2, 2, 2) - .addComponent(jUnknownLabel) - .addGap(2, 2, 2)) - ); + setPreferredSize(new java.awt.Dimension(0, 0)); - setBackground(new java.awt.Color(255, 255, 255)); - setPreferredSize(new java.awt.Dimension(495, 358)); - - jScrollPane.setBackground(new java.awt.Color(255, 255, 255)); + jScrollPane.setPreferredSize(new java.awt.Dimension(16, 16)); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -275,13 +237,17 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte } } javax.swing.JPanel contextContainer = new javax.swing.JPanel(); - contextContainer.add(jSourcePanel); contextContainer.setLayout(new BoxLayout(contextContainer, BoxLayout.Y_AXIS)); + contextContainer.setBorder(new EmptyBorder(ContentViewerDefaults.getPanelInsets())); + + contextContainer.add(jSourcePanel); + if (contextSourcePanels.isEmpty()) { contextContainer.add(jUnknownPanel); } else { for (javax.swing.JPanel sourcePanel : contextSourcePanels) { contextContainer.add(sourcePanel); + contextContainer.setAlignmentX(0); } } contextContainer.add(jUsagePanel); @@ -290,10 +256,11 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte } else { for (javax.swing.JPanel usagePanel : contextUsagePanels) { contextContainer.add(usagePanel); + contextContainer.setAlignmentX(0); } } - contextContainer.setBackground(javax.swing.UIManager.getDefaults().getColor("window")); + contextContainer.setBackground(ContentViewerDefaults.getPanelBackground()); contextContainer.setEnabled(foundASource); contextContainer.setVisible(foundASource); jScrollPane.getViewport().setView(contextContainer); @@ -346,6 +313,8 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte String sourceName = Bundle.ContextViewer_attachmentSource(); String sourceText = msgArtifactToAbbreviatedString(associatedArtifact); ContextSourcePanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact, dateTime); + sourcePanel.setBorder(new EmptyBorder(DATA_ROW_INSETS)); + sourcePanel.setAlignmentX(0); contextSourcePanels.add(sourcePanel); } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == associatedArtifact.getArtifactTypeID() @@ -353,18 +322,24 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte String sourceName = Bundle.ContextViewer_downloadSource(); String sourceText = webDownloadArtifactToString(associatedArtifact); ContextSourcePanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact, dateTime); + sourcePanel.setBorder(new EmptyBorder(DATA_ROW_INSETS)); + sourcePanel.setAlignmentX(0); contextSourcePanels.add(sourcePanel); } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == associatedArtifact.getArtifactTypeID()) { String sourceName = Bundle.ContextViewer_recentDocs(); String sourceText = recentDocArtifactToString(associatedArtifact); - ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime); + ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime); + usagePanel.setBorder(new EmptyBorder(DATA_ROW_INSETS)); + usagePanel.setAlignmentX(0); contextUsagePanels.add(usagePanel); } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == associatedArtifact.getArtifactTypeID()) { String sourceName = Bundle.ContextViewer_programExecution(); String sourceText = programExecArtifactToString(associatedArtifact); - ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime); + ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime); + usagePanel.setBorder(new EmptyBorder(DATA_ROW_INSETS)); + usagePanel.setAlignmentX(0); contextUsagePanels.add(usagePanel); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/layout/ContentViewerDefaults.java b/Core/src/org/sleuthkit/autopsy/contentviewers/layout/ContentViewerDefaults.java new file mode 100644 index 0000000000..65023b9110 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/layout/ContentViewerDefaults.java @@ -0,0 +1,167 @@ +/* + * 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.layout; + +import java.awt.Font; +import java.awt.Insets; +import java.awt.Toolkit; +import java.awt.Color; +import javax.swing.UIManager; + +/** + * Default values for layout of content values. + */ +public class ContentViewerDefaults { + + private static final Font DEFAULT_FONT = UIManager.getDefaults().getFont("Label.font"); + + // based on https://stackoverflow.com/questions/5829703/java-getting-a-font-with-a-specific-height-in-pixels/26564924#26564924 + private static final Double PT_TO_PX = Toolkit.getDefaultToolkit().getScreenResolution() / 72.0; + + private static final int DEFAULT_FONT_PX = (int) Math.round(DEFAULT_FONT.getSize() * PT_TO_PX); + + private static final Font SUB_HEADER_FONT = DEFAULT_FONT.deriveFont(Font.BOLD); + + private static final Font HEADER_FONT = DEFAULT_FONT.deriveFont(Font.BOLD, DEFAULT_FONT.getSize() + 2); + + private static final Font MESSAGE_FONT = DEFAULT_FONT.deriveFont(Font.ITALIC); + + private static final Font MONOSPACED_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT.getSize()); + + private static final Insets DEFAULT_PANEL_INSETS = UIManager.getDefaults().getInsets("TextPane.margin"); + + private static final int DEFAULT_INDENT = DEFAULT_FONT_PX; + private static final int DEFAULT_SECTION_SPACING = DEFAULT_FONT_PX; + + private static final int DEFAULT_COLUMN_SPACING = (int) Math.round((double) DEFAULT_FONT_PX / 3); + + private static final int DEFAULT_LINE_SPACING = (int) Math.round((double) DEFAULT_FONT_PX / 5); + + private static final Color DEFAULT_BACKGROUND = UIManager.getColor("Panel.background"); + + /** + * Returns the horizontal spacing between columns in a table in pixels. + * + * @return The horizontal spacing between columns in a table in pixels. + */ + public static int getColumnSpacing() { + return DEFAULT_COLUMN_SPACING; + } + + /** + * Returns the default font to be used. + * + * @return the default font to be used. + */ + public static Font getFont() { + return DEFAULT_FONT; + } + + /** + * Returns the font to be displayed for messages. + * + * @return The font to be displayed for messages. + */ + public static Font getMessageFont() { + return MESSAGE_FONT; + } + + /** + * Returns the font to be displayed for messages. + * + * @return The font to be displayed for messages. + */ + public static Font getHeaderFont() { + return HEADER_FONT; + } + + /** + * Returns the font to be displayed for sub headers. + * + * @return The font to be displayed for sub headers. + */ + public static Font getSubHeaderFont() { + return SUB_HEADER_FONT; + } + + /** + * Returns the font to be used for normal monospace. + * + * @return The font to be used for normal monospace. + */ + public static Font getMonospacedFont() { + return MONOSPACED_FONT; + } + + /** + * Returns the insets of the content within the parent content viewer panel. + * + * @return The insets of the content within the parent content viewer panel. + */ + public static Insets getPanelInsets() { + return DEFAULT_PANEL_INSETS; + } + + /** + * Returns the size in pixels that sections should be indented. + * + * @return The size in pixels that sections should be indented. + */ + public static Integer getSectionIndent() { + return DEFAULT_INDENT; + } + + /** + * Returns the spacing between sections in pixels. + * + * @return The spacing between sections in pixels. + */ + public static Integer getSectionSpacing() { + return DEFAULT_SECTION_SPACING; + } + + /** + * Returns the spacing between lines of text in pixels. + * + * @return The spacing between lines of text in pixels. + */ + public static Integer getLineSpacing() { + return DEFAULT_LINE_SPACING; + } + + /** + * Returns the color to be used as the background of the panel. + * + * @return The color to be used as the background of the panel. + */ + public static Color getPanelBackground() { + return DEFAULT_BACKGROUND; + } + + /** + * Returns the ratio of point size to pixel size for the user's screen + * resolution. + * + * @return The ratio of point size to pixel size for the user's screen + * resolution. + */ + public static Double getPtToPx() { + return PT_TO_PX; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/layout/ContentViewerHtmlStyles.java b/Core/src/org/sleuthkit/autopsy/contentviewers/layout/ContentViewerHtmlStyles.java new file mode 100644 index 0000000000..17632dc802 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/layout/ContentViewerHtmlStyles.java @@ -0,0 +1,194 @@ +/* + * 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.layout; + +import java.awt.Font; +import javax.swing.JTextPane; +import javax.swing.text.EditorKit; +import javax.swing.text.html.HTMLEditorKit; +import javax.swing.text.html.StyleSheet; + +/** + * The style sheet an class names to be used with content viewers using html + * rendering. + */ +public class ContentViewerHtmlStyles { + + // html stylesheet classnames for components + private static final String CLASS_PREFIX = ContentViewerHtmlStyles.class.getSimpleName(); + + private static final String HEADER_CLASSNAME = CLASS_PREFIX + "header"; + private static final String SUB_HEADER_CLASSNAME = CLASS_PREFIX + "subHeader"; + + private static final String MESSAGE_CLASSNAME = CLASS_PREFIX + "message"; + private static final String TEXT_CLASSNAME = CLASS_PREFIX + "text"; + private static final String MONOSPACED_CLASSNAME = CLASS_PREFIX + "monospaced"; + private static final String INDENTED_CLASSNAME = CLASS_PREFIX + "indent"; + private static final String SPACED_SECTION_CLASSNAME = CLASS_PREFIX + "spacedSection"; + private static final String KEY_COLUMN_TD_CLASSNAME = CLASS_PREFIX + "keyKolumn"; + + private static final Font DEFAULT_FONT = ContentViewerDefaults.getFont(); + private static final Font MESSAGE_FONT = ContentViewerDefaults.getMessageFont(); + private static final Font HEADER_FONT = ContentViewerDefaults.getHeaderFont(); + private static final Font SUB_HEADER_FONT = ContentViewerDefaults.getSubHeaderFont(); + private static final Font MONOSPACED_FONT = ContentViewerDefaults.getMonospacedFont(); + + // additional styling for components + private static final String STYLE_SHEET_RULE + = String.format(" .%s { font-family: %s; font-size: %dpt;font-style:italic; margin: 0px; padding: 0px 0px %dpt 0px; } ", + MESSAGE_CLASSNAME, MESSAGE_FONT.getFamily(), MESSAGE_FONT.getSize(), pxToPt(ContentViewerDefaults.getLineSpacing())) + + String.format(" .%s { font-family: %s; font-size: %dpt; font-weight: bold; margin: 0px; padding: 0px 0px %dpt 0px; } ", + HEADER_CLASSNAME, HEADER_FONT.getFamily(), HEADER_FONT.getSize(), pxToPt(ContentViewerDefaults.getLineSpacing())) + + String.format(" .%s { font-family: %s; font-size: %dpt; font-weight: bold; margin: 0px; padding: 0px 0px %dpt 0px; } ", + SUB_HEADER_CLASSNAME, SUB_HEADER_FONT.getFamily(), SUB_HEADER_FONT.getSize(), pxToPt(ContentViewerDefaults.getLineSpacing())) + + String.format(" .%s { font-family: %s; font-size: %dpt; margin: 0px; padding: 0px 0px %dpt 0px; } ", + TEXT_CLASSNAME, DEFAULT_FONT.getFamily(), DEFAULT_FONT.getSize(), pxToPt(ContentViewerDefaults.getLineSpacing())) + + String.format(" .%s { font-family: %s; font-size: %dpt; margin: 0px; padding: 0px 0px %dpt 0px; } ", + MONOSPACED_CLASSNAME, Font.MONOSPACED, MONOSPACED_FONT.getSize(), pxToPt(ContentViewerDefaults.getLineSpacing())) + + String.format(" .%s { padding-left: %dpt } ", + INDENTED_CLASSNAME, pxToPt(ContentViewerDefaults.getSectionIndent())) + + String.format(" .%s { padding-top: %dpt } ", + SPACED_SECTION_CLASSNAME, pxToPt(ContentViewerDefaults.getSectionSpacing())) + + String.format(" .%s { padding-right: %dpt } ", + KEY_COLUMN_TD_CLASSNAME, pxToPt(ContentViewerDefaults.getColumnSpacing())); + + private static final StyleSheet STYLE_SHEET = new StyleSheet(); + + static { + // add the style rule to the style sheet. + STYLE_SHEET.addRule(STYLE_SHEET_RULE); + } + + /** + * Converts pixel size to point size. The html rendering seems more + * consistent with point size versus pixel size. + * + * @param px The pixel size. + * + * @return The point size. + */ + private static int pxToPt(int px) { + return (int) Math.round(((double) px) / ContentViewerDefaults.getPtToPx()); + } + + /** + * Returns the class name to use for header text. + * + * @return The class name to use for header text. + */ + public static String getHeaderClassName() { + return HEADER_CLASSNAME; + } + + /** + * Returns the class name to use for sub header text. + * + * @return The class name to use for sub header text. + */ + public static String getSubHeaderClassName() { + return SUB_HEADER_CLASSNAME; + } + + /** + * Returns the class name to use for message text. + * + * @return The class name to use for message text. + */ + public static String getMessageClassName() { + return MESSAGE_CLASSNAME; + } + + /** + * Returns the class name to use for regular text. + * + * @return The class name to use for regular text. + */ + public static String getTextClassName() { + return TEXT_CLASSNAME; + } + + /** + * Returns the class name to use for monospaced text. + * + * @return The class name to use for monospaced text. + */ + public static String getMonospacedClassName() { + return MONOSPACED_CLASSNAME; + } + + /** + * Returns the class name to use for an indented (left padding) section. + * + * @return The class name to use for an indented (left padding) section. + */ + public static String getIndentedClassName() { + return INDENTED_CLASSNAME; + } + + /** + * Returns the class name to use for a section with spacing (top padding) + * section. + * + * @return The class name to use for a section with spacing (top padding) + * section. + */ + public static String getSpacedSectionClassName() { + return SPACED_SECTION_CLASSNAME; + } + + /** + * Returns the class name to use for a key column with right spacing (right + * padding). + * + * @return The class name to use for a key column with right spacing (right + * padding). + */ + public static String getKeyColumnClassName() { + return KEY_COLUMN_TD_CLASSNAME; + } + + /** + * If the textPane has an HTMLEditorKit, specifies the + * ContentViewerHTMLStyles styles to use refreshing the styles. + * + * @param textPane The text pane. + */ + public static void setStyles(JTextPane textPane) { + EditorKit editorKit = textPane.getEditorKit(); + if (editorKit instanceof HTMLEditorKit) { + ((HTMLEditorKit) editorKit).setStyleSheet(STYLE_SHEET); + } + } + + /** + * Sets up a JTextPane for html rendering using the css class names + * specified in this class. + * + * @param textPane The JTextPane to set up for content viewer html + * rendering. + */ + public static void setupHtmlJTextPane(JTextPane textPane) { + textPane.setContentType("text/html;charset=UTF-8"); //NON-NLS + HTMLEditorKit kit = new HTMLEditorKit(); + textPane.setEditorKit(kit); + kit.setStyleSheet(STYLE_SHEET); + textPane.setMargin(ContentViewerDefaults.getPanelInsets()); + textPane.setBackground(ContentViewerDefaults.getPanelBackground()); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java index 2c88660cb0..3ab02ed4d7 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.contentviewers.osaccount; import java.awt.BorderLayout; -import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; @@ -38,8 +37,10 @@ import javax.swing.Box; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingWorker; +import javax.swing.border.EmptyBorder; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerDefaults; import org.sleuthkit.autopsy.contentviewers.osaccount.SectionData.RowData; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.DataSource; @@ -61,7 +62,11 @@ public class OsAccountDataPanel extends JPanel { private static final int KEY_COLUMN = 0; private static final int VALUE_COLUMN = 1; - + + private final static Insets FIRST_HEADER_INSETS = new Insets(0, 0, 0, 0); + 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 static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MMM dd yyyy", US); private PanelDataFetcher dataFetcher = null; @@ -76,6 +81,7 @@ public class OsAccountDataPanel extends JPanel { */ private void initialize() { this.setLayout(new GridBagLayout()); + this.setBorder(new EmptyBorder(ContentViewerDefaults.getPanelInsets())); } /** @@ -269,7 +275,7 @@ public class OsAccountDataPanel extends JPanel { private void addTitle(String title, int row) { JLabel label = new JLabel(title); // Make the title bold. - label.setFont(label.getFont().deriveFont(Font.BOLD)); + label.setFont(ContentViewerDefaults.getHeaderFont()); add(label, getTitleContraints(row)); } @@ -312,7 +318,9 @@ public class OsAccountDataPanel extends JPanel { constraints.anchor = GridBagConstraints.NORTHWEST; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.weightx = 1; - constraints.insets = new Insets(5, 5, 5, 9); + constraints.insets = (row == 0) + ? FIRST_HEADER_INSETS + : HEADER_INSETS; return constraints; } @@ -332,7 +340,7 @@ public class OsAccountDataPanel extends JPanel { constraints.gridwidth = 1; // The title goes across the other columns constraints.gridheight = 1; constraints.anchor = GridBagConstraints.WEST; - constraints.insets = new Insets(0, 13, 5, 5); + constraints.insets = KEY_COLUMN_INSETS; return constraints; } @@ -352,8 +360,8 @@ public class OsAccountDataPanel extends JPanel { constraints.gridwidth = 1; // The title goes across the other columns constraints.gridheight = 1; constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.insets = VALUE_COLUMN_INSETS; constraints.weightx = 1; - constraints.insets = new Insets(0, 5, 5, 5); return constraints; } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutoWrappingJTextPane.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutoWrappingJTextPane.java index 1e4c1f4c63..652034dacc 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutoWrappingJTextPane.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutoWrappingJTextPane.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.corecomponents; +import java.text.MessageFormat; import javax.swing.JTextPane; import javax.swing.SizeRequirements; import javax.swing.text.Element; @@ -27,6 +28,8 @@ import javax.swing.text.ViewFactory; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.InlineView; import javax.swing.text.html.ParagraphView; +import javax.swing.text.html.StyleSheet; +import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerDefaults; import org.sleuthkit.autopsy.coreutils.EscapeUtil; /** @@ -96,9 +99,15 @@ public class AutoWrappingJTextPane extends JTextPane { this.setEditorKit(editorKit); } + + @Override public void setText(String text) { - super.setText("
" + EscapeUtil.escapeHtml(text) + "
"); + // setting the text format with style to avoid problems with overridden styles. + String style = String.format("font-family: %s; font-size: %dpt; margin: 0px; padding: 0px 0px %dpx 0px;", + ContentViewerDefaults.getFont().getFamily(), ContentViewerDefaults.getFont().getSize(), ContentViewerDefaults.getLineSpacing()); + + super.setText(MessageFormat.format("
{1}
", style, EscapeUtil.escapeHtml(text))); } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index e8c5ae4aea..3c0686c1a0 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -21,8 +21,6 @@ package org.sleuthkit.autopsy.corecomponents; import com.google.common.eventbus.Subscribe; import java.awt.Component; import java.awt.Cursor; -import java.awt.FontMetrics; -import java.awt.Graphics; import java.awt.dnd.DnDConstants; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -49,6 +47,7 @@ import javax.swing.JTable; import javax.swing.ListSelectionModel; import static javax.swing.SwingConstants.CENTER; import javax.swing.SwingUtilities; +import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableColumnModelEvent; @@ -106,6 +105,27 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(DataResultViewerTable.class.getName()); + // How many rows to sample in order to determine column width. + private static final int SAMPLE_ROW_NUM = 100; + + // The padding to be added in addition to content size when considering column width. + private static final int COLUMN_PADDING = 15; + + // The minimum column width. + private static final int MIN_COLUMN_WIDTH = 30; + + // The maximum column width. + private static final int MAX_COLUMN_WIDTH = 300; + + // The minimum row height to use when calculating whether scroll bar will be used. + private static final int MIN_ROW_HEIGHT = 10; + + // The width of the scroll bar. + private static final int SCROLL_BAR_WIDTH = ((Integer) UIManager.get("ScrollBar.width")).intValue(); + + // Any additional padding to be used for the first column. + private static final int FIRST_COL_ADDITIONAL_WIDTH = 0; + private static final String NOTEPAD_ICON_PATH = "org/sleuthkit/autopsy/images/notepad16.png"; private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png"; private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png"; @@ -152,7 +172,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * OutlineView to the actions global context. * * @param explorerManager The explorer manager of the ancestor top - * component. + * component. */ public DataResultViewerTable(ExplorerManager explorerManager) { this(explorerManager, Bundle.DataResultViewerTable_title()); @@ -165,8 +185,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * in the OutlineView to the actions global context. * * @param explorerManager The explorer manager of the ancestor top - * component. - * @param title The title. + * component. + * @param title The title. */ public DataResultViewerTable(ExplorerManager explorerManager, String title) { super(explorerManager); @@ -361,15 +381,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * If the given node is not null and has children, set it as the * root context of the child OutlineView, otherwise make an * "empty"node the root context. - * - * IMPORTANT NOTE: This is the first of many times where a - * getChildren call on the current root node causes all of the - * children of the root node to be created and defeats lazy child - * node creation, if it is enabled. It also likely leads to many - * case database round trips. */ if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) { this.getExplorerManager().setRootContext(this.rootNode); + outline.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); setupTable(); } else { Node emptyNode = new AbstractNode(Children.LEAF); @@ -422,13 +437,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { firstProp = props.remove(0); } - /* - * show the horizontal scroll panel and show all the content & header If - * there is only one column (which was removed from props above) Just - * let the table resize itself. - */ - outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF); - assignColumns(props); // assign columns to match the properties if (firstProp != null) { ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName()); @@ -513,87 +521,59 @@ public class DataResultViewerTable extends AbstractDataResultViewer { /* * Sets the column widths for the child OutlineView of this tabular results - * viewer. + * viewer providing any additional width to last column. */ protected void setColumnWidths() { - if (rootNode.getChildren().getNodesCount() != 0) { - final Graphics graphics = outlineView.getGraphics(); + // based on https://stackoverflow.com/questions/17627431/auto-resizing-the-jtable-column-widths + final TableColumnModel columnModel = outline.getColumnModel(); - if (graphics != null) { - // Current width of the outlineView - double outlineViewWidth = outlineView.getSize().getWidth(); - // List of the column widths - List columnWidths = new ArrayList<>(); - final FontMetrics metrics = graphics.getFontMetrics(); + // the remaining table width that can be used in last row + double availableTableWidth = outlineView.getSize().getWidth(); - int margin = 4; - int padding = 8; + for (int columnIdx = 0; columnIdx < outline.getColumnCount(); columnIdx++) { + int columnPadding = (columnIdx == 0) ? FIRST_COL_ADDITIONAL_WIDTH + COLUMN_PADDING : COLUMN_PADDING; + TableColumn tableColumn = columnModel.getColumn(columnIdx); - int totalColumnWidth = 0; - int cntMaxSizeColumns = 0; + // The width of this column + int width = MIN_COLUMN_WIDTH; - // Calulate the width for each column keeping track of the number - // of columns that were set to columnwidthLimit. - for (int column = 0; column < outline.getModel().getColumnCount(); column++) { - int firstColumnPadding = (column == 0) ? 32 : 0; - int columnWidthLimit = (column == 0) ? 350 : 300; - int valuesWidth = 0; - - // find the maximum width needed to fit the values for the first 100 rows, at most - for (int row = 0; row < Math.min(100, outline.getRowCount()); row++) { - TableCellRenderer renderer = outline.getCellRenderer(row, column); - Component comp = outline.prepareRenderer(renderer, row, column); - valuesWidth = Math.max(comp.getPreferredSize().width, valuesWidth); - } - - int headerWidth = metrics.stringWidth(outline.getColumnName(column)); - valuesWidth += firstColumnPadding; // add extra padding for first column - - int columnWidth = Math.max(valuesWidth, headerWidth); - columnWidth += 2 * margin + padding; // add margin and regular padding - - columnWidth = Math.min(columnWidth, columnWidthLimit); - columnWidths.add(columnWidth); - - totalColumnWidth += columnWidth; - - if (columnWidth == columnWidthLimit) { - cntMaxSizeColumns++; - } - } - - // Figure out how much extra, if any can be given to the columns - // so that the table is as wide as outlineViewWidth. If cntMaxSizeColumns - // is greater than 0 divide the extra space between the columns - // that could use more space. Otherwise divide evenly amoung - // all columns. - int extraWidth = 0; - - if (totalColumnWidth < outlineViewWidth) { - if (cntMaxSizeColumns > 0) { - extraWidth = (int) ((outlineViewWidth - totalColumnWidth) / cntMaxSizeColumns); - } else { - extraWidth = (int) ((outlineViewWidth - totalColumnWidth) / columnWidths.size()); - } - } - - for (int column = 0; column < columnWidths.size(); column++) { - int columnWidth = columnWidths.get(column); - - if (cntMaxSizeColumns > 0) { - if (columnWidth >= ((column == 0) ? 350 : 300)) { - columnWidth += extraWidth; - } - } else { - columnWidth += extraWidth; - } - - outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth); - } + // get header cell width + // taken in part from https://stackoverflow.com/a/18381924 + TableCellRenderer headerRenderer = tableColumn.getHeaderRenderer(); + if (headerRenderer == null) { + headerRenderer = outline.getTableHeader().getDefaultRenderer(); + } + Object headerValue = tableColumn.getHeaderValue(); + Component headerComp = headerRenderer.getTableCellRendererComponent(outline, headerValue, false, false, 0, columnIdx); + width = Math.max(headerComp.getPreferredSize().width + columnPadding, width); + + // get the max of row widths from the first SAMPLE_ROW_NUM rows + Component comp = null; + int rowCount = outline.getRowCount(); + for (int row = 0; row < Math.min(rowCount, SAMPLE_ROW_NUM); row++) { + TableCellRenderer renderer = outline.getCellRenderer(row, columnIdx); + comp = outline.prepareRenderer(renderer, row, columnIdx); + width = Math.max(comp.getPreferredSize().width + columnPadding, width); + } + + // no higher than maximum column width + if (width > MAX_COLUMN_WIDTH) { + width = MAX_COLUMN_WIDTH; + } + + // if last column, calculate remaining width factoring in the possibility of a scroll bar. + if (columnIdx == outline.getColumnCount() - 1) { + int rowHeight = comp == null ? MIN_ROW_HEIGHT : comp.getPreferredSize().height; + if (headerComp.getPreferredSize().height + rowCount * rowHeight > outlineView.getSize().getHeight()) { + availableTableWidth -= SCROLL_BAR_WIDTH; + } + + columnModel.getColumn(columnIdx).setPreferredWidth(Math.max(width, (int) availableTableWidth)); + } else { + // otherwise set preferred width to width and decrement availableTableWidth accordingly + columnModel.getColumn(columnIdx).setPreferredWidth(width); + availableTableWidth -= width; } - } else { - // if there's no content just auto resize all columns - outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); } } @@ -749,7 +729,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * order. * * @return a List> of the properties in the persisted - * order. + * order. */ private synchronized List> loadColumnOrder() { @@ -1266,18 +1246,20 @@ public class DataResultViewerTable extends AbstractDataResultViewer { /** * Returns the icon denoted by the Score's Significance. + * * @param significance The Score's Significance. + * * @return The icon (or null) related to that significance. */ private ImageIcon getIcon(Significance significance) { if (significance == null) { return null; } - + switch (significance) { case NOTABLE: return NOTABLE_ICON_SCORE; - case LIKELY_NOTABLE: + case LIKELY_NOTABLE: return INTERESTING_SCORE_ICON; case LIKELY_NONE: case NONE: @@ -1286,7 +1268,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { return null; } } - + @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index dabb45ede2..0a43d47102 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -112,7 +112,7 @@ public abstract class AbstractContentNode extends ContentNode * @param lookup The Lookup object for the node. */ AbstractContentNode(T content, Lookup lookup) { - super(Children.create(new ContentChildren(content), false), lookup); + super(Children.create(new ContentChildren(content), true), lookup); this.content = content; //super.setName(ContentUtils.getSystemName(content)); super.setName("content_" + Long.toString(content.getId())); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentChildren.java index 44885e1ef8..7b0987f623 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentChildren.java @@ -103,7 +103,21 @@ class ContentChildren extends AbstractContentChildren { @Override protected List makeKeys() { - return getDisplayChildren(parent); + List contentList = getDisplayChildren(parent); + + // Call the getUniquePath method to cache the value for future use + // in the EDT + contentList.forEach(content->{ + try { + content.getUniquePath(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Failed attempt to cache the " + + "unique path of the abstract file instance. Name: %s (objID=%d)", + content.getName(), content.getId()), ex); + } + }); + + return contentList; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceFilesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceFilesNode.java index fd45f69bf0..b7e33860b9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceFilesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceFilesNode.java @@ -69,7 +69,7 @@ public class DataSourceFilesNode extends DisplayableItemNode { } public DataSourceFilesNode(long dsObjId) { - super(Children.create(new DataSourcesNodeChildren(dsObjId), false), Lookups.singleton(NAME)); + super(Children.create(new DataSourcesNodeChildren(dsObjId), true), Lookups.singleton(NAME)); displayName = (dsObjId > 0) ? NbBundle.getMessage(DataSourceFilesNode.class, "DataSourcesNode.group_by_datasource.name") : NAME; init(); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index c5fae53c03..4546290007 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -122,7 +122,7 @@ public class DataSourcesNode extends DisplayableItemNode { * Main constructor. */ DataSourcesNode() { - super(Children.create(new DataSourcesByTypeChildren(), false), Lookups.singleton(NAME)); + super(Children.create(new DataSourcesByTypeChildren(), true), Lookups.singleton(NAME)); setName(NAME); setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java index 08ce15cab6..1f349fdfc4 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java @@ -212,7 +212,7 @@ public class HostNode extends DisplayableItemNode { * @param hosts The HostDataSources key. */ HostNode(HostDataSources hosts) { - this(Children.create(new HostGroupingChildren(HOST_DATA_SOURCES, hosts.getHost()), false), hosts.getHost()); + this(Children.create(new HostGroupingChildren(HOST_DATA_SOURCES, hosts.getHost()), true), hosts.getHost()); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index ea0e2bb347..5df888052b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -20,8 +20,6 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; @@ -35,7 +33,6 @@ import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.DeleteDataSourceAction; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.datasourcesummary.ui.ViewSummaryInformationAction; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; @@ -48,7 +45,6 @@ import org.sleuthkit.autopsy.ingest.ModuleContentEvent; import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.RunIngestModulesAction; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException; @@ -171,17 +167,10 @@ public class ImageNode extends AbstractContentNode { Bundle.ImageNode_createSheet_timezone_desc(), this.content.getTimeZone())); - try (CaseDbQuery query = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery("SELECT device_id FROM data_source_info WHERE obj_id = " + this.content.getId());) { - ResultSet deviceIdSet = query.getResultSet(); - if (deviceIdSet.next()) { - sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_deviceId_name(), + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_deviceId_name(), Bundle.ImageNode_createSheet_deviceId_displayName(), Bundle.ImageNode_createSheet_deviceId_desc(), - deviceIdSet.getString("device_id"))); - } - } catch (SQLException | TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Failed to get device id for the following image: " + this.content.getId(), ex); - } + content.getDeviceId())); return sheet; } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/PersonNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/PersonNode.java index 99db59211b..06d52b8662 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/PersonNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/PersonNode.java @@ -203,7 +203,7 @@ public class PersonNode extends DisplayableItemNode { * @param displayName The display name for the person. */ private PersonNode(Person person, String displayName) { - super(Children.create(new PersonChildren(person), false), + super(Children.create(new PersonChildren(person), true), person == null ? Lookups.fixed(displayName) : Lookups.fixed(person, displayName)); super.setName(displayName); super.setDisplayName(displayName); diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryNode.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryNode.java index a4c48e7adf..4aa3cf1be3 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,7 +30,6 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.datasourcesummary.ui.Bundle; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.datamodel.DataSource; @@ -59,7 +58,7 @@ final class DataSourceSummaryNode extends AbstractNode { * DataSources which are this nodes children */ DataSourceSummaryNode(List dataSourceList) { - super(Children.create(new DataSourceSummaryChildren(dataSourceList), false)); + super(Children.create(new DataSourceSummaryChildren(dataSourceList), true)); } /** diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 35b5a7e560..6fa1e93c5a 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -46,6 +46,7 @@ import net.sf.sevenzipjbinding.PropID; import net.sf.sevenzipjbinding.SevenZip; import net.sf.sevenzipjbinding.SevenZipException; import net.sf.sevenzipjbinding.SevenZipNativeInitializationException; +import org.apache.tika.Tika; import org.apache.tika.parser.txt.CharsetDetector; import org.apache.tika.parser.txt.CharsetMatch; import org.netbeans.api.progress.ProgressHandle; @@ -194,6 +195,15 @@ class SevenZipExtractor { } return false; } + + boolean isSevenZipExtractionSupported(String mimeType) { + for (SupportedArchiveExtractionFormats supportedMimeType : SupportedArchiveExtractionFormats.values()) { + if (mimeType.contains(supportedMimeType.toString())) { + return true; + } + } + return false; + } /** * Private helper method to standardize the cancellation check that is @@ -789,33 +799,18 @@ class SevenZipExtractor { // add them to the DB. We wait until the end so that we have the metadata on all of the // intermediate nodes since the order is not guaranteed try { - unpackedTree.updateOrAddFileToCaseRec(statusMap, archiveFilePath); + unpackedTree.updateOrAddFileToCaseRec(statusMap, archiveFilePath, parentAr, archiveFile, depthMap); if (checkForIngestCancellation(archiveFile)) { return false; } - unpackedFiles = unpackedTree.getAllFileObjects(); - //check if children are archives, update archive depth tracking - for (int i = 0; i < unpackedFiles.size(); i++) { - if (checkForIngestCancellation(archiveFile)) { - return false; - } - progress.progress(String.format("%s: Searching for nested archives (%d of %d)", currentArchiveName, i + 1, unpackedFiles.size())); - AbstractFile unpackedFile = unpackedFiles.get(i); - if (unpackedFile == null) { - continue; - } - if (isSevenZipExtractionSupported(unpackedFile)) { - Archive child = new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile); - parentAr.addChild(child); - depthMap.put(unpackedFile.getId(), child); - } - unpackedFile.close(); - } } catch (TskCoreException | NoCurrentCaseException e) { logger.log(Level.SEVERE, "Error populating complete derived file hierarchy from the unpacked dir structure", e); //NON-NLS //TODO decide if anything to cleanup, for now bailing } + + // Get the new files to be added to the case. + unpackedFiles = unpackedTree.getAllFileObjects(); } catch (SevenZipException | IllegalArgumentException ex) { logger.log(Level.WARNING, "Error unpacking file: " + archiveFile, ex); //NON-NLS @@ -991,6 +986,8 @@ class SevenZipExtractor { private EncodedFileOutputStream output; private String localAbsPath; private int bytesWritten; + private static final Tika tika = new Tika(); + private String mimeType = ""; UnpackStream(String localAbsPath) throws IOException { this.output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1); @@ -1003,6 +1000,7 @@ class SevenZipExtractor { this.output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1); this.localAbsPath = localAbsPath; this.bytesWritten = 0; + this.mimeType = ""; } public int getSize() { @@ -1012,6 +1010,10 @@ class SevenZipExtractor { @Override public int write(byte[] bytes) throws SevenZipException { try { + // Detect MIME type now while the file is in memory + if (bytesWritten == 0) { + mimeType = tika.detect(bytes); + } output.write(bytes); this.bytesWritten += bytes.length; } catch (IOException ex) { @@ -1023,6 +1025,10 @@ class SevenZipExtractor { return bytes.length; } + public String getMIMEType() { + return mimeType; + } + public void close() throws IOException { try (EncodedFileOutputStream out = output) { out.flush(); @@ -1196,6 +1202,8 @@ class SevenZipExtractor { 0L, createTimeInSeconds, accessTimeInSeconds, modTimeInSeconds, localRelPath); return; + } else { + unpackedNode.setMimeType(unpackStream.getMIMEType()); } final String localAbsPath = archiveDetailsMap.get( @@ -1413,10 +1421,10 @@ class SevenZipExtractor { * Traverse the tree top-down after unzipping is done and create derived * files for the entire hierarchy */ - void updateOrAddFileToCaseRec(HashMap statusMap, String archiveFilePath) throws TskCoreException, NoCurrentCaseException { + void updateOrAddFileToCaseRec(HashMap statusMap, String archiveFilePath, Archive parentAr, AbstractFile archiveFile, ConcurrentHashMap depthMap) throws TskCoreException, NoCurrentCaseException { final FileManager fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); for (UnpackedNode child : rootNode.getChildren()) { - updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath); + updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath, parentAr, archiveFile, depthMap); } } @@ -1434,7 +1442,7 @@ class SevenZipExtractor { * * @throws TskCoreException */ - private void updateOrAddFileToCaseRec(UnpackedNode node, FileManager fileManager, HashMap statusMap, String archiveFilePath) throws TskCoreException { + private void updateOrAddFileToCaseRec(UnpackedNode node, FileManager fileManager, HashMap statusMap, String archiveFilePath, Archive parentAr, AbstractFile archiveFile, ConcurrentHashMap depthMap) throws TskCoreException { DerivedFile df; progress.progress(String.format("%s: Adding/updating files in case database (%d of %d)", currentArchiveName, ++nodesProcessed, numItems)); try { @@ -1497,10 +1505,17 @@ class SevenZipExtractor { } } } + + // Check for zip bombs + if (isSevenZipExtractionSupported(node.getMimeType())) { + Archive child = new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile); + parentAr.addChild(child); + depthMap.put(node.getFile().getId(), child); + } //recurse adding the children if this file was incomplete the children presumably need to be added for (UnpackedNode child : node.getChildren()) { - updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath)); + updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath), parentAr, archiveFile, depthMap); } } @@ -1517,6 +1532,7 @@ class SevenZipExtractor { private long size; private long ctime, crtime, atime, mtime; private boolean isFile; + private String mimeType = ""; private UnpackedNode parent; //root constructor @@ -1593,6 +1609,14 @@ class SevenZipExtractor { void setFile(AbstractFile file) { this.file = file; } + + void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + + String getMimeType() { + return mimeType; + } /** * get child by name or null if it doesn't exist diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java index 8c7ba7cdd9..9d72539841 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,7 +40,7 @@ final class AinStatusNode extends AbstractNode { * Construct a new AinStatusNode. */ AinStatusNode(AutoIngestMonitor monitor) { - super(Children.create(new AinStatusChildren(monitor), false)); + super(Children.create(new AinStatusChildren(monitor), true)); } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index ffe8e19f01..3142181117 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2018-2019 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -69,7 +69,7 @@ final class AutoIngestJobsNode extends AbstractNode { * refresh events */ AutoIngestJobsNode(AutoIngestMonitor monitor, AutoIngestJobStatus status, EventBus eventBus) { - super(Children.create(new AutoIngestNodeChildren(monitor, status, eventBus), false)); + super(Children.create(new AutoIngestNodeChildren(monitor, status, eventBus), true)); refreshChildrenEventBus = eventBus; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index 8ff9c0fdcc..c64cee2565 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.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"); @@ -29,10 +29,10 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import javax.swing.JLabel; import javax.swing.SizeRequirements; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; +import javax.swing.UIManager; import javax.swing.text.Element; import javax.swing.text.View; import javax.swing.text.ViewFactory; @@ -59,7 +59,7 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP private static final Logger logger = Logger.getLogger(ExtractedContentPanel.class.getName()); // set font as close as possible to default - private static final Font DEFAULT_FONT = new JLabel().getFont(); + private static final Font DEFAULT_FONT = UIManager.getDefaults().getFont("Label.font"); private static final long serialVersionUID = 1L; private String contentName; @@ -72,7 +72,6 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP ExtractedContentPanel() { initComponents(); additionalInit(); - setSources("", new ArrayList<>()); hitPreviousButton.setEnabled(false); hitNextButton.setEnabled(false); @@ -135,7 +134,7 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP }; } }; - // get the style sheet for editing font size + // set new style sheet to clear default styles styleSheet = editorKit.getStyleSheet(); sourceComboBox.addItemListener(itemEvent -> { @@ -144,6 +143,7 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP } }); extractedTextPane.setComponentPopupMenu(rightClickMenu); + copyMenuItem.addActionListener(actionEvent -> extractedTextPane.copy()); selectAllMenuItem.addActionListener(actionEvent -> extractedTextPane.selectAll()); @@ -156,11 +156,16 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP if (zoomPanel instanceof TextZoomPanel) ((TextZoomPanel) this.zoomPanel).resetSize(); }); + + setSources("", new ArrayList<>()); } private void setStyleSheetSize(StyleSheet styleSheet, int size) { - styleSheet.addRule("body {font-family:\"" + DEFAULT_FONT.getFamily() + "\"; font-size:" + size + "pt; } "); + styleSheet.addRule( + "body { font-family:\"" + DEFAULT_FONT.getFamily() + "\"; font-size:" + size + "pt; } " + + "pre { font-family:\"" + DEFAULT_FONT.getFamily() + "\"; font-size:" + size + "pt; } " + ); } @@ -499,6 +504,8 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP extractedTextPane.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); } + // refresh style + setStyleSheetSize(styleSheet, curSize); extractedTextPane.setText(safeText); extractedTextPane.setCaretPosition(0); } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java index 9435161a23..f3e073e7f9 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java @@ -596,8 +596,9 @@ final class ChromeCacheExtractor { if (fileCopyCache.containsKey(fileTableKey)) { return Optional.of(fileCopyCache.get(fileTableKey).getAbstractFile()); } - - List cacheFiles = fileManager.findFiles(dataSource, cacheFileName, cacheFolderName); //NON-NLS + + List cacheFiles = currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, + cacheFileName, cacheFolderName); if (!cacheFiles.isEmpty()) { // Sort the list for consistency. Preference is: // - In correct subfolder and allocated diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java index a3db639b32..371394ff10 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java @@ -633,7 +633,7 @@ class Chromium extends Extract { BlackboardArtifact webDownloadArtifact = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadFile, bbattributes); bbartifacts.add(webDownloadArtifact); String normalizedFullPath = FilenameUtils.normalize(fullPath, true); - for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(normalizedFullPath), FilenameUtils.getPath(normalizedFullPath))) { + for (AbstractFile downloadedFile : currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, FilenameUtils.getName(normalizedFullPath), FilenameUtils.getPath(normalizedFullPath))) { bbartifacts.add(createAssociatedArtifact(downloadedFile, webDownloadArtifact)); break; } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DataSourceUsageAnalyzer.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DataSourceUsageAnalyzer.java index bd6b5836d5..0e4fa5ecf1 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DataSourceUsageAnalyzer.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DataSourceUsageAnalyzer.java @@ -157,17 +157,13 @@ class DataSourceUsageAnalyzer extends Extract { * does not exist with the given description. * * @param osType - the OS_TYPE to check for - * - * @return true if any specified files exist false if none exist */ private void checkIfOsSpecificVolume(ExtractOs.OS_TYPE osType) throws TskCoreException { - FileManager fileManager = currentCase.getServices().getFileManager(); for (String filePath : osType.getFilePaths()) { - for (AbstractFile file : fileManager.findFiles(dataSource, FilenameUtils.getName(filePath), FilenameUtils.getPath(filePath))) { - if ((file.getParentPath() + file.getName()).equals(filePath)) { - createDataSourceUsageArtifact(osType.getDsUsageLabel()); - return; - } + for (AbstractFile file : currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, + FilenameUtils.getName(filePath), FilenameUtils.getPath(filePath))) { + createDataSourceUsageArtifact(osType.getDsUsageLabel()); + return; } } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractOs.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractOs.java index a76c1652dd..6bc00ada63 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractOs.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractOs.java @@ -117,12 +117,10 @@ class ExtractOs extends Extract { * search for */ private AbstractFile getFirstFileFound(List pathsToSearchFor) throws TskCoreException{ - FileManager fileManager = currentCase.getServices().getFileManager(); for (String filePath : pathsToSearchFor) { - for (AbstractFile file : fileManager.findFiles(dataSource, FilenameUtils.getName(filePath), FilenameUtils.getPath(filePath))) { - if ((file.getParentPath() + file.getName()).equals(filePath)) { - return file; - } + List files = currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, FilenameUtils.getName(filePath), FilenameUtils.getPath(filePath)); + if (!files.isEmpty()) { + return files.get(0); } } return null; diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java index 3fd6142d53..bc5a4de60e 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java @@ -1686,18 +1686,13 @@ class ExtractRegistry extends Extract { * @returnv BlackboardArtifact or a null value */ private BlackboardArtifact createAssociatedArtifact(String filePathName, BlackboardArtifact bba) { - org.sleuthkit.autopsy.casemodule.services.FileManager fileManager = currentCase.getServices().getFileManager(); String fileName = FilenameUtils.getName(filePathName); String filePath = FilenameUtils.getPath(filePathName); List sourceFiles; try { - sourceFiles = fileManager.findFiles(dataSource, fileName, filePath); //NON-NLS + sourceFiles = currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, fileName, filePath); if (!sourceFiles.isEmpty()) { - for (AbstractFile sourceFile : sourceFiles) { - if (sourceFile.getParentPath().endsWith(filePath)) { - return createAssociatedArtifact(sourceFile, bba); - } - } + return createAssociatedArtifact(sourceFiles.get(0), bba); } } catch (TskCoreException ex) { // only catching the error and displaying the message as the file may not exist on the diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java index 2bf0351542..429666f8d6 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java @@ -647,8 +647,6 @@ final class ExtractSafari extends Extract { Long time = null; Long pathID = null; - FileManager fileManager = getCurrentCase().getServices().getFileManager(); - NSString nsstring = (NSString) entry.get(PLIST_KEY_DOWNLOAD_URL); if (nsstring != null) { url = nsstring.toString(); @@ -669,7 +667,8 @@ final class ExtractSafari extends Extract { bbartifacts.add(webDownloadArtifact); // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact. - for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(path), FilenameUtils.getPath(path))) { + for (AbstractFile downloadedFile : currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, + FilenameUtils.getName(path), FilenameUtils.getPath(path))) { bbartifacts.add(createAssociatedArtifact(downloadedFile, webDownloadArtifact)); break; } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java index 6dd30ed4cc..87bbb0232d 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java @@ -592,7 +592,8 @@ class Firefox extends Extract { bbartifacts.add(webDownloadArtifact); // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact. - for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) { + for (AbstractFile downloadedFile : currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, + FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) { bbartifacts.add(createAssociatedArtifact(downloadedFile, webDownloadArtifact)); break; } @@ -727,7 +728,8 @@ class Firefox extends Extract { bbartifacts.add(webDownloadArtifact); // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact. - for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) { + for (AbstractFile downloadedFile : currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, + FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) { bbartifacts.add(createAssociatedArtifact(downloadedFile, webDownloadArtifact)); break; } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RecentDocumentsByLnk.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RecentDocumentsByLnk.java index b0c119ae25..d9ad0fbfb8 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RecentDocumentsByLnk.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RecentDocumentsByLnk.java @@ -152,13 +152,12 @@ class RecentDocumentsByLnk extends Extract { * @returnv BlackboardArtifact or a null value */ private BlackboardArtifact createAssociatedArtifact(String filePathName, BlackboardArtifact bba) { - org.sleuthkit.autopsy.casemodule.services.FileManager fileManager = currentCase.getServices().getFileManager(); String normalizePathName = FilenameUtils.normalize(filePathName, true); String fileName = FilenameUtils.getName(normalizePathName); String filePath = FilenameUtils.getPath(normalizePathName); List sourceFiles; try { - sourceFiles = fileManager.findFiles(dataSource, fileName, filePath); //NON-NLS + sourceFiles = currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, fileName, filePath); for (AbstractFile sourceFile : sourceFiles) { if (sourceFile.getParentPath().endsWith(filePath)) { return createAssociatedArtifact(sourceFile, bba); diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java index ff95c60ca8..766c3c61de 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java @@ -135,8 +135,7 @@ class Util { parent_path = parent_path.substring(0, index); List files = null; try { - FileManager fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); - files = fileManager.findFiles(dataSource, name, parent_path); + files = Case.getCurrentCaseThrows().getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, name, parent_path); } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.WARNING, "Error fetching 'index.data' files for Internet Explorer history."); //NON-NLS }