From 70886b74881bf900c1d40d1aeedac1bf1ab3f1c8 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 22 Aug 2023 12:59:43 -0400 Subject: [PATCH] GP-3425 - Listing Fields - Simplified word wrapping --- .../viewer/field/EolCommentFieldFactory.java | 8 +- .../util/viewer/field/OperandFieldHelper.java | 5 +- .../util/viewer/field/PlateFieldFactory.java | 60 +++++----- .../viewer/field/PostCommentFieldFactory.java | 8 +- .../viewer/field/PreCommentFieldFactory.java | 9 +- .../fieldpanel/support/FieldUtils.java | 106 +++++++----------- 6 files changed, 83 insertions(+), 113 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java index 5e754da173..6479120d85 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java @@ -47,7 +47,7 @@ public class EolCommentFieldFactory extends FieldFactory { private static final String GROUP_TITLE = "EOL Comments Field"; private static final String SEMICOLON_PREFIX = "; "; public static final String ENABLE_WORD_WRAP_MSG = - GROUP_TITLE + Options.DELIMITER + "Enable Word Wrapping"; + GROUP_TITLE + Options.DELIMITER + FieldUtils.WORD_WRAP_OPTION_NAME; public static final String MAX_DISPLAY_LINES_MSG = GROUP_TITLE + Options.DELIMITER + "Maximum Lines To Display"; public static final String ENABLE_SHOW_SEMICOLON_MSG = @@ -106,11 +106,7 @@ public class EolCommentFieldFactory extends FieldFactory { fieldOptions.registerOption(MAX_DISPLAY_LINES_MSG, 6, hl, "The maximum number of lines used to display the end-of-line comment."); fieldOptions.registerOption(ENABLE_WORD_WRAP_MSG, false, hl, - "Enables word wrapping in the end-of-line comments field. If word " + - "wrapping is on, user enter new lines are ignored and the entire comment is" + - " displayed in paragraph form. If word wrapping is off, comments are " + - "displayed in line format however the user entered them. Lines that are too " + - "long for the field, are truncated."); + FieldUtils.WORD_WRAP_OPTION_DESCRIPTION); fieldOptions.registerOption(ENABLE_SHOW_SEMICOLON_MSG, false, hl, "Displays a semi-colon before each line in the end-of-line comment. " + diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java index f2510b9126..c3d456b94f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java @@ -23,8 +23,7 @@ import java.util.List; import javax.swing.event.ChangeListener; import docking.widgets.fieldpanel.field.*; -import docking.widgets.fieldpanel.support.FieldLocation; -import docking.widgets.fieldpanel.support.RowColLocation; +import docking.widgets.fieldpanel.support.*; import ghidra.GhidraOptions; import ghidra.app.util.*; import ghidra.app.util.viewer.field.ListingColors.FunctionColors; @@ -50,7 +49,7 @@ import ghidra.util.HelpLocation; abstract class OperandFieldHelper extends FieldFactory { private final static String ENABLE_WORD_WRAP_MSG = - GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + "Enable Word Wrapping"; + GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + FieldUtils.WORD_WRAP_OPTION_NAME; private final static String MAX_DISPLAY_LINES_MSG = GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + "Maximum Lines To Display"; private final static String UNDERLINE_OPTION = diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java index b44ae2a374..b19be7c7b4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java @@ -51,7 +51,7 @@ public class PlateFieldFactory extends FieldFactory { public static final Color DEFAULT_COLOR = Palette.BLUE; private final static String FIELD_GROUP_TITLE = "Plate Comments Field"; public final static String ENABLE_WORD_WRAP_MSG = - FIELD_GROUP_TITLE + Options.DELIMITER + "Enable Word Wrapping"; + FIELD_GROUP_TITLE + Options.DELIMITER + FieldUtils.WORD_WRAP_OPTION_NAME; /** * This is the length of the padding, which is a '*' and a space on each side @@ -245,13 +245,15 @@ public class PlateFieldFactory extends FieldFactory { for (String c : comments) { commentsList.add(CommentUtils.parseTextForAnnotations(c, p, prototype, row++)); } + if (isWordWrap) { - int charWidth = getMetrics().charWidth(' '); + int spaceWidth = getMetrics().charWidth(' '); + int starWidth = getMetrics().charWidth('*'); + int charWidth = Math.max(spaceWidth, starWidth); int paddingWidth = CONTENT_PADDING * charWidth; - commentsList = FieldUtils.wordWrapList( - new CompositeFieldElement(commentsList), - Math.max(width - paddingWidth, charWidth)); + commentsList = FieldUtils.wrap(commentsList, Math.max(width - paddingWidth, charWidth)); } + boolean isClipped = addSideBorders(commentsList); elements.addAll(commentsList); @@ -274,27 +276,36 @@ public class PlateFieldFactory extends FieldFactory { private FieldElementResult addSideBorder(FieldElement element, int row, boolean center) { - int ellipsisLength = 0; - int availableWidth = stars.length() - CONTENT_PADDING; + boolean isClipped = false; + int ellipsisWidth = 0; String ellipsisText = EMPTY_STRING; - if (element.length() > availableWidth) { + + int spaceWidth = getMetrics().charWidth(' '); + int starWidth = getMetrics().charWidth('*'); + int fullStarWidth = stars.length() * starWidth; + int sideStarWidth = 2 * starWidth; + int sideSpaceWidth = 2 * spaceWidth; + int availableWidth = fullStarWidth - sideStarWidth - sideSpaceWidth; + if (availableWidth < element.getStringWidth()) { // not enough room; clip the text and add ellipses + isClipped = true; ellipsisText = ELLIPSIS; - ellipsisLength = ELLIPSIS.length(); - availableWidth = stars.length() - CONTENT_PADDING - ellipsisLength; - element = element.substring(0, availableWidth); // clip + ellipsisWidth = getMetrics().charWidth('.') * ELLIPSIS.length(); + availableWidth -= ellipsisWidth; + int charsThatFit = element.getMaxCharactersForWidth(availableWidth); + element = element.substring(0, charsThatFit); // clip } - int charWidth = getMetrics().charWidth(' '); - int paddingWidth = (CONTENT_PADDING + ellipsisLength) * charWidth; - int textWidth = paddingWidth + element.getStringWidth(); - int totalPadding = (width - textWidth) / charWidth; - int prePadding = center ? totalPadding / 2 : 0; - int postPadding = center ? (totalPadding + 1) / 2 : totalPadding; + int paddingWidth = sideStarWidth + sideSpaceWidth; + int currentTextWidth = paddingWidth + element.getStringWidth() + ellipsisWidth; + int biggestCharWidth = Math.max(starWidth, spaceWidth); + int paddingCharsNeeded = (width - currentTextWidth) / biggestCharWidth; + int prePaddingCharCount = center ? paddingCharsNeeded / 2 : 0; + int postPaddingCharCount = center ? (paddingCharsNeeded + 1) / 2 : paddingCharsNeeded; StringBuilder buffy = new StringBuilder(); buffy.append('*').append(' '); - addPadding(buffy, prePadding); + addPaddingSpaces(buffy, prePaddingCharCount); FieldElement prefix = new TextFieldElement( new AttributedString(buffy.toString(), CommentColors.PLATE, getMetrics()), row, 0); @@ -304,7 +315,7 @@ public class PlateFieldFactory extends FieldFactory { prefix.length() + element.length()); buffy.setLength(0); - addPadding(buffy, postPadding); + addPaddingSpaces(buffy, postPaddingCharCount); buffy.append(' ').append('*'); FieldElement suffix = new TextFieldElement( @@ -313,10 +324,10 @@ public class PlateFieldFactory extends FieldFactory { return new FieldElementResult( new CompositeFieldElement(new FieldElement[] { prefix, element, ellipsis, suffix }), - ellipsisLength > 0); + isClipped); } - private void addPadding(StringBuilder buf, int count) { + private void addPaddingSpaces(StringBuilder buf, int count) { for (int i = 0; i < count; i++) { buf.append(' '); } @@ -688,12 +699,7 @@ public class PlateFieldFactory extends FieldFactory { options.getOptions(GROUP_TITLE).setOptionsHelpLocation(help); options.registerOption(ENABLE_WORD_WRAP_MSG, false, null, - "Enables word wrapping in the pre-comments field. If word " + - "wrapping is on, user enter new lines are ignored and " + - "the entire comment is displayed in paragraph form. If word " + - "wrapping is off, comments are displayed in line format " + - "however the user entered them. Lines that are too long " + - "for the field, are truncated."); + FieldUtils.WORD_WRAP_OPTION_DESCRIPTION); options.registerOption(SHOW_SUBROUTINE_PLATES_OPTION, true, help, "Toggle for whether a plate comment should be displayed for subroutines."); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java index f414f78533..7ed5a334c2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java @@ -52,7 +52,7 @@ public class PostCommentFieldFactory extends FieldFactory { private final static String GROUP_TITLE = "Format Code"; private final static String FIELD_GROUP_TITLE = "Post-comments Field"; public final static String ENABLE_WORD_WRAP_MSG = - FIELD_GROUP_TITLE + Options.DELIMITER + "Enable Word Wrapping"; + FIELD_GROUP_TITLE + Options.DELIMITER + FieldUtils.WORD_WRAP_OPTION_NAME; public final static String ENABLE_ALWAYS_SHOW_AUTOMATIC_MSG = FIELD_GROUP_TITLE + Options.DELIMITER + "Always Show the Automatic Comment"; @@ -516,11 +516,7 @@ public class PostCommentFieldFactory extends FieldFactory { private void init(Options options) { options.registerOption(ENABLE_WORD_WRAP_MSG, false, null, - "Enables word wrapping in the pre-comments field. " + - "If word wrapping is on, user enter" + " new lines are ignored and the entire " + - "comment is displayed in paragraph form. " + " If word wrapping is off, comments" + - " are displayed in line format however the user entered " + - "them. Lines that are too long for the field, are truncated."); + FieldUtils.WORD_WRAP_OPTION_DESCRIPTION); options.registerOption(FLAG_FUNCTION_EXIT_OPTION, false, null, "Toggle for whether a post comment should be displayed " + diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PreCommentFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PreCommentFieldFactory.java index 6b3de481e8..d651af5b2c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PreCommentFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PreCommentFieldFactory.java @@ -50,7 +50,7 @@ public class PreCommentFieldFactory extends FieldFactory { private final static String GROUP_TITLE = "Format Code"; private final static String FIELD_GROUP_TITLE = "Pre-comments Field"; public final static String ENABLE_WORD_WRAP_MSG = - FIELD_GROUP_TITLE + Options.DELIMITER + "Enable Word Wrapping"; + FIELD_GROUP_TITLE + Options.DELIMITER + FieldUtils.WORD_WRAP_OPTION_NAME; public final static String ENABLE_ALWAYS_SHOW_AUTOMATIC_MSG = FIELD_GROUP_TITLE + Options.DELIMITER + "Always Show the Automatic Comment"; @@ -394,12 +394,7 @@ public class PreCommentFieldFactory extends FieldFactory { private void init(Options options) { options.registerOption(ENABLE_WORD_WRAP_MSG, false, null, - "Enables word wrapping in the pre-comments field. If word " + - "wrapping is on, user enter new lines are ignored and the " + - "entire comment is displayed in paragraph form. If word " + - "wrapping is off, comments are displayed in line format " + - "however the user entered them. Lines that are too long " + - "for the field, are truncated."); + FieldUtils.WORD_WRAP_OPTION_DESCRIPTION); options.registerOption(ENABLE_ALWAYS_SHOW_AUTOMATIC_MSG, true, null, "Toggles the display of the automatic pre-comment"); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java index 8a3130dc7b..df60e11255 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java @@ -15,7 +15,8 @@ */ package docking.widgets.fieldpanel.support; -import java.util.*; +import java.util.ArrayList; +import java.util.List; import docking.widgets.fieldpanel.field.FieldElement; @@ -24,7 +25,11 @@ import docking.widgets.fieldpanel.field.FieldElement; */ public class FieldUtils { - private static final char[] WHITE_SPACE = new char[] { '\t', '\n', '\r', '\f' }; + public static final String WORD_WRAP_OPTION_NAME = "Enable Word Wrapping"; + public static final String WORD_WRAP_OPTION_DESCRIPTION = + "Enables word wrapping. When on, each line of text is wrapped as needed to fit within " + + "the current width. When off, comments are displayed as entered by the user. Lines " + + "that are too long for the field are truncated."; private FieldUtils() { // utility class } @@ -32,7 +37,7 @@ public class FieldUtils { public static List wrap(List fieldElements, int width) { List wrappedElements = new ArrayList<>(); for (FieldElement fieldElement : fieldElements) { - wrappedElements.addAll(wordWrapList(fieldElement, width)); + wrappedElements.addAll(wrap(fieldElement, width)); } return wrappedElements; } @@ -45,23 +50,20 @@ public class FieldUtils { * @return The wrapped elements */ public static List wrap(FieldElement fieldElement, int width) { - - FieldElement originalFieldElement = fieldElement.replaceAll(WHITE_SPACE, ' '); - if (originalFieldElement.getStringWidth() <= width) { - return Arrays.asList(originalFieldElement); - } - List lines = new ArrayList<>(); - int wordWrapPos = findWordWrapPosition(originalFieldElement, width); - while (wordWrapPos > 0) { - lines.add(originalFieldElement.substring(0, wordWrapPos)); - if (originalFieldElement.charAt(wordWrapPos) == ' ') { - wordWrapPos++; // skip white space char - } - originalFieldElement = originalFieldElement.substring(wordWrapPos); - wordWrapPos = findWordWrapPosition(originalFieldElement, width); + if (fieldElement.getStringWidth() <= width) { + lines.add(fieldElement); + return lines; } - lines.add(originalFieldElement); + + FieldElement element = fieldElement; + int wordWrapPos = findWordWrapPosition(element, width); + while (wordWrapPos > 0) { + lines.add(element.substring(0, wordWrapPos)); + element = element.substring(wordWrapPos); + wordWrapPos = findWordWrapPosition(element, width); + } + lines.add(element); return lines; } @@ -76,57 +78,32 @@ public class FieldUtils { */ public static List wrap(FieldElement fieldElement, int width, boolean breakOnWhiteSpace) { + if (breakOnWhiteSpace) { return wrap(fieldElement, width); } - FieldElement originalFieldElement = fieldElement.replaceAll(WHITE_SPACE, ' '); - if (originalFieldElement.getStringWidth() <= width) { - return Arrays.asList(originalFieldElement); - } List lines = new ArrayList<>(); - int wordWrapPos = originalFieldElement.getMaxCharactersForWidth(width); - if (wordWrapPos == originalFieldElement.length()) { - wordWrapPos = 0; - } - while (wordWrapPos > 0) { - lines.add(originalFieldElement.substring(0, wordWrapPos)); - originalFieldElement = originalFieldElement.substring(wordWrapPos); - wordWrapPos = originalFieldElement.getMaxCharactersForWidth(width); - if (wordWrapPos == originalFieldElement.length()) { - wordWrapPos = 0; - } - } - lines.add(originalFieldElement); - return lines; - } - - /** - * Splits the given FieldElement into sub-elements by wrapping the element on whitespace. - * - * @param fieldElement The element to wrap - * @param width The maximum width to allow before wrapping - * @return The wrapped elements - */ - public static List wordWrapList(FieldElement fieldElement, int width) { - List lines = new ArrayList<>(); - - FieldElement originalFieldElement = fieldElement.replaceAll(WHITE_SPACE, ' '); - if (originalFieldElement.getStringWidth() <= width) { - lines.add(originalFieldElement); + if (fieldElement.getStringWidth() <= width) { + lines.add(fieldElement); return lines; } - int wordWrapPos = findWordWrapPosition(originalFieldElement, width); - while (wordWrapPos > 0) { - lines.add(originalFieldElement.substring(0, wordWrapPos)); - if (originalFieldElement.charAt(wordWrapPos) == ' ') { - wordWrapPos++; // skip white space char - } - originalFieldElement = originalFieldElement.substring(wordWrapPos); - wordWrapPos = findWordWrapPosition(originalFieldElement, width); + FieldElement element = fieldElement; + int wordWrapPos = element.getMaxCharactersForWidth(width); + if (wordWrapPos == element.length()) { + wordWrapPos = 0; } - lines.add(originalFieldElement); + + while (wordWrapPos > 0) { + lines.add(element.substring(0, wordWrapPos)); + element = element.substring(wordWrapPos); + wordWrapPos = element.getMaxCharactersForWidth(width); + if (wordWrapPos == element.length()) { + wordWrapPos = 0; + } + } + lines.add(element); return lines; } @@ -134,13 +111,13 @@ public class FieldUtils { * Finds the position within the given element at which to split the line for word wrapping. * This method finds the last whitespace character that completely fits within the given width. * If there is no whitespace character before the width break point, it finds the first - * whitespace character after the width. If no whitespace can be found, then the text will - * be split at a non-whitespace character. + * whitespace character after the width. If no whitespace can be found, then 0 will be returned + * to signal that there is no spot to break the line. * * @param element the element to split * @param width the max width to allow before looking for a word wrap positions * @return 0 if the element cannot be split, else the character position of the string - * to be split off. + * to be split, exclusive */ private static int findWordWrapPosition(FieldElement element, int width) { @@ -150,9 +127,10 @@ public class FieldUtils { return 0; } + // inclusive int whiteSpacePosition = text.lastIndexOf(" ", wrapPosition - 1); if (whiteSpacePosition >= 0) { - return whiteSpacePosition; + return whiteSpacePosition + 1; // exclusive } return wrapPosition;