Merge remote-tracking branch

'origin/GP-3425-dragonmacher-field-word-wrapping--SQUASHED'
(Closes #5299, #5298)
This commit is contained in:
Ryan Kurtz 2023-08-23 08:00:40 -04:00
commit e6ca9675cc
6 changed files with 83 additions and 113 deletions

View file

@ -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. " +

View file

@ -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 =

View file

@ -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.");

View file

@ -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 " +

View file

@ -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");

View file

@ -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<FieldElement> wrap(List<FieldElement> fieldElements, int width) {
List<FieldElement> 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<FieldElement> wrap(FieldElement fieldElement, int width) {
FieldElement originalFieldElement = fieldElement.replaceAll(WHITE_SPACE, ' ');
if (originalFieldElement.getStringWidth() <= width) {
return Arrays.asList(originalFieldElement);
}
List<FieldElement> 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<FieldElement> 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<FieldElement> 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<FieldElement> wordWrapList(FieldElement fieldElement, int width) {
List<FieldElement> 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;