From 51af44f843ceaa58c55860fb35a30a5524887904 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 21 Sep 2021 12:56:33 -0400 Subject: [PATCH] Xref merge test fixes --- .../CommentFieldSearcher.java | 5 +- .../util/viewer/field/BytesFieldFactory.java | 2 - .../util/viewer/field/PlateFieldFactory.java | 233 +++++++++++------- .../util/viewer/field/XRefFieldFactory.java | 2 +- .../codebrowser/CodeBrowserOptionsTest.java | 7 +- .../CodeBrowserScreenMovementTest.java | 45 ++-- .../searchtext/SearchTextPlugin1Test.java | 8 +- .../viewer/field/PlateFieldFactoryTest.java | 45 ++-- .../fieldpanel/field/ClippingTextField.java | 52 ++-- .../field/CompositeFieldElement.java | 108 ++++---- .../CompositeVerticalLayoutTextField.java | 7 +- .../fieldpanel/field/FlowLayoutTextField.java | 29 ++- .../field/VerticalLayoutTextField.java | 154 ++++++++---- .../fieldpanel/FlowLayoutTextFieldTest.java | 29 ++- .../CompositeVerticalLayoutTextFieldTest.java | 2 +- .../core/clipboard/ClipboardPluginTest.java | 6 +- 16 files changed, 440 insertions(+), 294 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/CommentFieldSearcher.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/CommentFieldSearcher.java index 1a6908213b..92f0db7140 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/CommentFieldSearcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/CommentFieldSearcher.java @@ -60,7 +60,8 @@ public class CommentFieldSearcher extends ProgramDatabaseFieldSearcher { return nextAddress; } - private void findMatchesForCurrentAddress(Address address, List currentMatches) { + private void findMatchesForCurrentAddress(Address address, + List currentMatches) { String comment = program.getListing().getComment(commentType, address); if (comment == null) { return; @@ -84,7 +85,7 @@ public class CommentFieldSearcher extends ProgramDatabaseFieldSearcher { charOffset, rowIndex); case CodeUnit.PLATE_COMMENT: return new PlateFieldLocation(program, address, dataPath, rowIndex, charOffset, - comments, rowIndex - 1); + comments, rowIndex); case CodeUnit.REPEATABLE_COMMENT: return new RepeatableCommentFieldLocation(program, address, dataPath, comments, rowIndex, charOffset, rowIndex); // TODO One of searchStrIndex parameters is wrong. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/BytesFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/BytesFieldFactory.java index 7792221b03..6caf5b9ea2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/BytesFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/BytesFieldFactory.java @@ -427,11 +427,9 @@ public class BytesFieldFactory extends FieldFactory { ListingTextField btf = (ListingTextField) bf; RowColLocation rcl = btf.dataToScreenLocation(tokenIndex, tokenOffset); - if (hasSamePath(bf, loc)) { return new FieldLocation(index, fieldNum, rcl.row(), rcl.col()); } - return null; } 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 04c478d956..adbfeda9cb 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 @@ -20,6 +20,8 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang3.StringUtils; + import docking.widgets.fieldpanel.field.*; import docking.widgets.fieldpanel.support.*; import ghidra.app.util.HelpTopics; @@ -32,6 +34,7 @@ import ghidra.app.util.viewer.proxy.ProxyObj; import ghidra.framework.options.Options; import ghidra.framework.options.ToolOptions; import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressOverflowException; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.program.util.*; @@ -141,21 +144,18 @@ public class PlateFieldFactory extends FieldFactory { } CodeUnit cu = (CodeUnit) proxy.getObject(); - List elements = new ArrayList<>(10); boolean isClipped = false; + List elements = new ArrayList<>(); String commentText = getCommentText(cu); - if ((commentText == null) || (commentText.isEmpty())) { - generateDefaultPlate(elements, cu); + if (StringUtils.isBlank(commentText)) { + getDefaultFieldElements(cu, elements); } else { - isClipped = generateFormattedPlateComment(elements, cu); + isClipped = getFormattedFieldElements(cu, elements); } - addBlankLines(elements, cu); - - if (elements.size() == 0) { - // no real or default comment - return null; + if (elements.isEmpty()) { + return null; // no real or default comments } if (isNestedDataAtSameAddressAsParent(proxy)) { @@ -170,6 +170,27 @@ public class PlateFieldFactory extends FieldFactory { return new PlateListingTextField(proxy, textField); } + private boolean getFormattedFieldElements(CodeUnit cu, List elements) { + + int numberBlankLines = getNumberBlankLines(cu, true); + + addBlankLines(elements, numberBlankLines, cu); + + String[] comments = cu.getCommentAsArray(CodeUnit.PLATE_COMMENT); + return generateFormattedPlateComment(elements, comments, cu.getProgram()); + } + + private void getDefaultFieldElements(CodeUnit cu, List elements) { + + int numberBlankLines = getNumberBlankLines(cu, true); + addBlankLines(elements, numberBlankLines, cu); + + String defaultComment = getDefaultComment(cu); + if (defaultComment != null) { + generateDefaultPlate(elements, defaultComment); + } + } + private boolean isNestedDataAtSameAddressAsParent(ProxyObj proxy) { if (proxy instanceof DataProxy) { DataProxy dp = (DataProxy) proxy; @@ -200,67 +221,39 @@ public class PlateFieldFactory extends FieldFactory { return buffy.toString(); } - /** + /* * Creates desired FieldElements and puts them in the given list. Returns true if any of the * data is clipped because it is too long to display. - * - * @param elementList the list into which the created data will be placed. - * @param cu the code unit for which a plate comment will be generated - * @return true if the data is clipped */ - private boolean generateFormattedPlateComment(List elementList, CodeUnit cu) { - String[] comments = cu.getCommentAsArray(CodeUnit.PLATE_COMMENT); - if ((comments == null) || (comments.length == 0)) { + private boolean generateFormattedPlateComment(List elements, String[] comments, + Program p) { + if (comments == null || comments.length == 0) { return false; } - Program program = cu.getProgram(); AttributedString prototype = new AttributedString(EMPTY_STRING, color, getMetrics()); - for (int i = 0; i < comments.length; i++) { - elementList - .add(CommentUtils.parseTextForAnnotations(comments[i], program, prototype, i)); + AttributedString asteriscs = getStarsString(); + int row = elements.size(); + + // add top border + elements.add(new TextFieldElement(asteriscs, row++, 0)); + + int commentsStart = row; + for (String c : comments) { + FieldElement element = CommentUtils.parseTextForAnnotations(c, p, prototype, row++); + elements.add(element); } if (isWordWrap) { - elementList = FieldUtils.wordWrapList(new CompositeFieldElement(elementList), width); + elements = FieldUtils.wordWrapList(new CompositeFieldElement(elements), width); } - return addBorder(elementList); - } + boolean isClipped = addSideBorders(elements, commentsStart); - private void addBlankLines(List elementList, CodeUnit cu) { - AttributedString prototype = new AttributedString(EMPTY_STRING, color, getMetrics()); - FieldElement blankLine = new TextFieldElement(prototype, 0, 0); - int numBlankLines = getNumberBlankLines(cu, elementList.size() > 0); - for (int i = 0; i < numBlankLines; i++) { - elementList.add(0, blankLine); - } - } + // add bottom border + elements.add(new TextFieldElement(asteriscs, row++, 0)); - private int getNumberBlankLines(CodeUnit cu, boolean hasPlate) { - if (cu.getProgram().getListing().getFunctionAt(cu.getMinAddress()) != null) { - if (nLinesBeforeFunctions != 0) { - return nLinesBeforeFunctions; - } - } - - if (hasPlate && nLinesBeforePlates != 0) { - return nLinesBeforePlates; - } - - if (cu.getLabel() != null) { - return nLinesBeforeLabels; - } - - return 0; - } - - private boolean addBorder(List elements) { - AttributedString asteriscs = getStarsString(); - boolean isClipped = addSideBorders(elements); - elements.add(0, new TextFieldElement(asteriscs, 0, 0)); - elements.add(new TextFieldElement(asteriscs, elements.size(), 0)); return isClipped; } @@ -269,13 +262,17 @@ public class PlateFieldFactory extends FieldFactory { * Text will be left justified between two '*' and padded based upon the * available field width. * @param elements the field elements that may get updated + * @param commentsStart the start index of comment elements in the element list * @return formatted plate text line */ - private boolean addSideBorders(List elements) { + private boolean addSideBorders(List elements, int commentsStart) { boolean isClipped = false; - if (elements.size() == 1) { + + // If there is only a single line comment, then center it + int n = elements.size() - commentsStart; + if (n == 1) { FieldElement element = elements.get(0); - if (element.length() > 1 && element.charAt(0) == ' ') { + if (element.length() > 1 && element.charAt(0) == ' ') { // not sure why the space matters FieldElementResult result = addSideBorder(element.substring(1), 1, true); isClipped = result.isClipped(); elements.set(0, result.getFieldElement()); @@ -283,8 +280,8 @@ public class PlateFieldFactory extends FieldFactory { } } - for (int i = 0; i < elements.size(); i++) { - FieldElementResult result = addSideBorder(elements.get(i), i + 1, false); + for (int i = commentsStart; i < elements.size(); i++) { + FieldElementResult result = addSideBorder(elements.get(i), i, false); isClipped |= result.isClipped(); elements.set(i, result.getFieldElement()); } @@ -311,7 +308,7 @@ public class PlateFieldFactory extends FieldFactory { int prePadding = center ? totalPadding / 2 : 0; int postPadding = center ? (totalPadding + 1) / 2 : totalPadding; - StringBuffer buffy = new StringBuffer(); + StringBuilder buffy = new StringBuilder(); buffy.append('*').append(' '); addPadding(buffy, prePadding); @@ -335,21 +332,56 @@ public class PlateFieldFactory extends FieldFactory { ellipsisLength > 0); } - private void addPadding(StringBuffer buf, int count) { + private void addPadding(StringBuilder buf, int count) { for (int i = 0; i < count; i++) { buf.append(' '); } } - private void generateDefaultPlate(List elementList, CodeUnit cu) { - String defaultComment = getDefaultComment(cu); - if (defaultComment != null) { - AttributedString as = new AttributedString(defaultComment, color, getMetrics()); - elementList.add(new TextFieldElement(as, 0, 0)); - addBorder(elementList); + private void addBlankLines(List elements, int numberBlankLines, CodeUnit cu) { + AttributedString prototype = new AttributedString(EMPTY_STRING, color, getMetrics()); + for (int row = 0; row < numberBlankLines; row++) { + elements.add(new TextFieldElement(prototype, row, 0)); } } + private int getNumberBlankLines(CodeUnit cu, boolean hasPlate) { + if (cu.getProgram().getListing().getFunctionAt(cu.getMinAddress()) != null) { + if (nLinesBeforeFunctions != 0) { + return nLinesBeforeFunctions; + } + } + + if (hasPlate && nLinesBeforePlates != 0) { + return nLinesBeforePlates; + } + + if (cu.getLabel() != null) { + return nLinesBeforeLabels; + } + + return 0; + } + + private void generateDefaultPlate(List elements, String defaultComment) { + if (defaultComment == null) { + return; + } + + AttributedString asteriscs = getStarsString(); + int row = elements.size(); + + // top border + elements.add(0, new TextFieldElement(asteriscs, row++, 0)); + + AttributedString as = new AttributedString(defaultComment, color, getMetrics()); + elements.add(new TextFieldElement(as, row++, 0)); + addSideBorders(elements, 1); + + // bottom border + elements.add(new TextFieldElement(asteriscs, row++, 0)); + } + private String getDefaultComment(CodeUnit cu) { if (showFunctionPlates) { @@ -367,11 +399,6 @@ public class PlateFieldFactory extends FieldFactory { return SUBROUTINE_PLATE_COMMENT; } -// TODO handle case statements? -// if (showCasePlates) { -// return CASE_PLATE_COMMENT; -// } - if (showTransitionPlates) { if (isDeadCode(cu)) { return DEAD_CODE_PLATE_COMMENT; @@ -432,7 +459,7 @@ public class PlateFieldFactory extends FieldFactory { int n = width / starWidth; if (stars.length() != n) { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); for (int i = 0; i < n; i++) { buf.append('*'); } @@ -465,18 +492,18 @@ public class PlateFieldFactory extends FieldFactory { ((ListingTextField) listingField).screenToDataLocation(row, col); // - // The 'row' value is an offset from the top of the plate comment, which has 0 or - // more blank lines at the top, followed by a line of asterisks. + // The 'row' value includes blank lines and header decoration lines. The 'commentRow' used + // below is the index into the list of comments. Calculate the comment beginning by + // removing the non-comment lines. // int fillerLineCount = getNumberOfLeadingFillerLines(listingField); - int commentRow = row - fillerLineCount; - if (commentRow >= comments.length) { - commentRow = -1; // clicked the bottom decoration line + if (commentRow >= comments.length || commentRow < 0) { + commentRow = -1; // clicked above the comment or the bottom decoration line } return new PlateFieldLocation(cu.getProgram(), ((CodeUnit) proxyObject).getMinAddress(), - cpath, dataLocation.row(), dataLocation.col(), comments, commentRow); + cpath, commentRow, dataLocation.col(), comments, commentRow); } private int getNumberOfLeadingFillerLines(ListingField listingField) { @@ -491,6 +518,7 @@ public class PlateFieldFactory extends FieldFactory { @Override public FieldLocation getFieldLocation(ListingField listingField, BigInteger index, int fieldNum, ProgramLocation programLoc) { + if (!(programLoc instanceof CommentFieldLocation)) { return null; } @@ -507,8 +535,40 @@ public class PlateFieldFactory extends FieldFactory { return null; } + /* + Calculate the data row using the model row provided in the location, along with + compensating for any spacing and plate comment decorations. For example, for this + comment, + + This is line one + This is line two + + the plate comment may look like this + + (blank line) + **************************** + * This is line one + * This is line two + ***************************** + */ + + CodeUnit cu = (CodeUnit) obj; + String commentText = getCommentText(cu); + boolean hasComment = true; + if (StringUtils.isBlank(commentText)) { + String defaultComment = getDefaultComment(cu); + if (defaultComment == null) { + hasComment = false; + } + } + + int commentRow = commentLocation.getRow(); + int numberBlankLines = getNumberBlankLines(cu, hasComment); + int headerCount = hasComment ? 1 : 0; + int dataRow = commentRow + numberBlankLines + headerCount; + ListingTextField listingTextField = (ListingTextField) listingField; - RowColLocation location = listingTextField.dataToScreenLocation(commentLocation.getRow(), + RowColLocation location = listingTextField.dataToScreenLocation(dataRow, commentLocation.getCharOffset()); return new FieldLocation(index, fieldNum, location.row(), location.col()); } @@ -614,7 +674,7 @@ public class PlateFieldFactory extends FieldFactory { Address prevAddr = cu.getMinAddress().subtractNoWrap(1); return cu.getProgram().getListing().getCodeUnitContaining(prevAddr); } - catch (Exception e) { + catch (AddressOverflowException e) { // we are just being lazy and not validating before doing the subtract--SOCK! } return null; @@ -638,7 +698,7 @@ public class PlateFieldFactory extends FieldFactory { } initialized = true; - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); sb.append("\n"); for (int i = 0; i < 19; i++) { sb.append("|"); @@ -725,14 +785,13 @@ public class PlateFieldFactory extends FieldFactory { int getLeadingFillerLineCount() { int count = 0; - for (Field field : subFields) { - String text = field.getText().trim(); + for (String line : getLines()) { count++; - if (text.isEmpty()) { + if (line.isEmpty()) { continue; // skip leading blank lines } - if (text.startsWith("*")) { + if (line.startsWith("*")) { break; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java index e8f3022c7d..1358606747 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java @@ -551,7 +551,7 @@ public class XRefFieldFactory extends FieldFactory { List elements = new ArrayList<>(); FunctionManager functionManager = program.getFunctionManager(); Function currentFunction = functionManager.getFunctionContaining(cu.getMinAddress()); - int n = tooMany ? maxXRefs + 1 : totalXrefs; + int n = tooMany ? maxXRefs : totalXrefs; int count = 0; for (; count < xrefs.size() && count < n; count++) { Reference ref = xrefs.get(count); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java index fd0791da01..28b84c3078 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserOptionsTest.java @@ -896,7 +896,8 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest assertEquals("XREFs Field.Display Local Block", names.get(1)); assertEquals("XREFs Field.Display Namespace", names.get(2)); assertEquals("XREFs Field.Display Reference Type", names.get(3)); - assertEquals("XREFs Field.Maximum Number of XREFs to Display", names.get(4)); + assertEquals("XREFs Field.Group by Function", names.get(4)); + assertEquals("XREFs Field.Maximum Number of XREFs to Display", names.get(5)); assertTrue(cb.goToField(addr("0x1003d9f"), "XRef", 0, 0)); @@ -968,7 +969,9 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest btf = (ListingTextField) cb.getCurrentField(); assertEquals(9, btf.getNumRows()); - options.setInt(names.get(4), 3); + // note: the 'group by function' option is tested in the XrefFieldFactoryTest + + options.setInt(names.get(5), 3); cb.updateNow(); assertTrue(cb.goToField(addr("0x1003d9f"), "XRef", 0, 0)); btf = (ListingTextField) cb.getCurrentField(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java index b4b0b56ee5..81314ac334 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java @@ -212,25 +212,25 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { for (int i = 0; i < 22; i++) { scrollbar.getUnitIncrement(1); } - waitForPostedSwingRunnables(); + waitForSwing(); assertEquals(addr("0x1001027"), codeBrowser.getAddressTopOfScreen()); for (int i = 0; i < 18; i++) { scrollbar.getUnitIncrement(-1); } - waitForPostedSwingRunnables(); + waitForSwing(); assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen()); for (int i = 0; i < 16; i++) { scrollbar.getBlockIncrement(1); } - waitForPostedSwingRunnables(); + waitForSwing(); assertEquals(addr("0x1001136"), codeBrowser.getAddressTopOfScreen()); for (int i = 0; i < 16; i++) { scrollbar.getBlockIncrement(-1); } - waitForPostedSwingRunnables(); + waitForSwing(); assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen()); } @@ -333,25 +333,25 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { for (int i = 0; i < 20; i++) { scrollbar.getUnitIncrement(1); } - waitForPostedSwingRunnables(); + waitForSwing(); assertEquals(addr("0x1001025"), codeBrowser.getAddressTopOfScreen()); for (int i = 0; i < 16; i++) { scrollbar.getUnitIncrement(-1); } - waitForPostedSwingRunnables(); + waitForSwing(); assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen()); for (int i = 0; i < 16; i++) { scrollbar.getBlockIncrement(1); } - waitForPostedSwingRunnables(); + waitForSwing(); assertEquals(addr("0x1001136"), codeBrowser.getAddressTopOfScreen()); for (int i = 0; i < 16; i++) { scrollbar.getBlockIncrement(-1); } - waitForPostedSwingRunnables(); + waitForSwing(); assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen()); } @@ -412,7 +412,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { codeBrowser.goToField(addr("0x1007000"), "+", 0, 0); click(codeBrowser, 1); - waitForPostedSwingRunnables(); + waitForSwing(); codeBrowser.goToField(addr("0x1007001"), "Address", 0, 0); assertEquals("01007001", codeBrowser.getCurrentFieldText()); @@ -423,7 +423,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { codeBrowser.goToField(addr("0x1007000"), "+", 0, 0); click(codeBrowser, 1); - waitForPostedSwingRunnables(); + waitForSwing(); codeBrowser.goToField(addr("0x1007000"), "Address", 0, 0); for (int i = 0; i < 7; i++) { @@ -450,7 +450,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { codeBrowser.goToField(addr("0x1007000"), "+", 0, 0); click(codeBrowser, 1); - waitForPostedSwingRunnables(); + waitForSwing(); codeBrowser.goToField(addr("0x1007008"), "Address", 0, 0); assertEquals("01007008", codeBrowser.getCurrentFieldText()); codeBrowser.goToField(addr("0x1007010"), "Address", 0, 0); @@ -458,7 +458,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { codeBrowser.goToField(addr("0x1007000"), "+", 0, 0); click(codeBrowser, 1); - waitForPostedSwingRunnables(); + waitForSwing(); codeBrowser.goToField(addr("0x1007000"), "Address", 0, 0); codeBrowser.goToField(addr("0x1007008"), "Address", 0, 0); assertEquals("01007000", codeBrowser.getCurrentFieldText()); @@ -477,14 +477,14 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { codeBrowser.goToField(addr("0x1007000"), "+", 0, 0); click(codeBrowser, 1); - waitForPostedSwingRunnables(); + waitForSwing(); codeBrowser.goToField(addr("0x1007023"), "+", 0, 0); click(codeBrowser, 1); - waitForPostedSwingRunnables(); + waitForSwing(); codeBrowser.goToField(addr("0x100702a"), "+", 0, 0); click(codeBrowser, 1); - waitForPostedSwingRunnables(); - waitForPostedSwingRunnables(); + waitForSwing(); + waitForSwing(); codeBrowser.goToField(addr("0x100702b"), "Address", 0, 0); assertEquals("0100702b", codeBrowser.getCurrentFieldText()); @@ -525,6 +525,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { goTo(codeBrowser, addr("0x1007000")); for (int i = 0; i < 300; i++) { + cursorRight(fp); if (!codeBrowser.getCurrentFieldText().equals(cb2.getCurrentFieldText())) { @@ -533,11 +534,11 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { assertEquals(codeBrowser.getCurrentFieldText(), cb2.getCurrentFieldText()); if (!codeBrowser.getCurrentFieldLoction().equals(cb2.getCurrentFieldLoction())) { - System.err.println("location no equal at cursor move: " + i); + System.err.println("location not equal at cursor move: " + i); } + assertEquals(codeBrowser.getCurrentFieldLoction(), cb2.getCurrentFieldLoction()); } - } @Test @@ -561,13 +562,13 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { codeBrowser.goToField(addr("0x1007000"), "+", 0, 0); click(codeBrowser, 1); - waitForPostedSwingRunnables(); + waitForSwing(); codeBrowser.goToField(addr("0x1007023"), "+", 0, 0); click(codeBrowser, 1); - waitForPostedSwingRunnables(); + waitForSwing(); codeBrowser.goToField(addr("0x100702a"), "+", 0, 0); click(codeBrowser, 1); - waitForPostedSwingRunnables(); + waitForSwing(); // this should open the other structure codeBrowser.goToField(addr("0x1007023"), "Address", 0, 0); @@ -809,7 +810,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { fieldOptions.setInt(name, 0); } } - waitForPostedSwingRunnables(); + waitForSwing(); plugin.updateNow(); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin1Test.java index 29f3917b68..98f31f1ef3 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin1Test.java @@ -187,7 +187,7 @@ public class SearchTextPlugin1Test extends AbstractGhidraHeadedIntegrationTest { assertNotNull(commentsCB); assertSelected(commentsCB); - // verify that the case sensitive checkbox is not selected + // verify that the case sensitive checkbox is not selected JCheckBox csCB = (JCheckBox) findButton(dialog.getComponent(), "Case Sensitive"); assertNotNull(csCB); assertNotSelected(csCB); @@ -216,7 +216,7 @@ public class SearchTextPlugin1Test extends AbstractGhidraHeadedIntegrationTest { @Test public void testSearchOptions() throws Exception { // verify that the search dialog allows for searching Functions, - // Comments, labels, instruction mnemonics and operands, defined data + // Comments, labels, instruction mnemonics and operands, defined data // mnemonics and values. SearchTextDialog dialog = getDialog(); JCheckBox cb = (JCheckBox) findButton(dialog.getComponent(), "Functions"); @@ -419,6 +419,8 @@ public class SearchTextPlugin1Test extends AbstractGhidraHeadedIntegrationTest { loc = cbPlugin.getCurrentLocation(); assertEquals(getAddr(0x01004192), loc.getAddress()); assertTrue(loc instanceof CommentFieldLocation); + assertEquals("Search result not placed at the matching character position", 15, + ((CommentFieldLocation) loc).getCharOffset()); assertEquals(CodeUnit.PLATE_COMMENT, ((CommentFieldLocation) loc).getCommentType()); pressButton(searchButton); @@ -667,7 +669,7 @@ public class SearchTextPlugin1Test extends AbstractGhidraHeadedIntegrationTest { // // test marker stuff - // + // AddressSet set = getAddressesFromModel(model); MarkerService markerService = tool.getService(MarkerService.class); MarkerSet markerSet = markerService.getMarkerSet("Search", program); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/PlateFieldFactoryTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/PlateFieldFactoryTest.java index d68cc87c88..93a92971ed 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/PlateFieldFactoryTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/PlateFieldFactoryTest.java @@ -15,8 +15,7 @@ */ package ghidra.app.util.viewer.field; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.util.ArrayList; import java.util.List; @@ -57,10 +56,6 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest { private Program program; private GoToService goToService; - public PlateFieldFactoryTest() { - super(); - } - @Before public void setUp() throws Exception { @@ -151,8 +146,9 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest { cu.setCommentAsArray(CodeUnit.PLATE_COMMENT, new String[] { "this is", "a plate comment" }); // create a reference to addr - program.getReferenceManager().addMemoryReference(getAddr(0x010023ee), addr, - RefType.DATA, SourceType.USER_DEFINED, 0); + program.getReferenceManager() + .addMemoryReference(getAddr(0x010023ee), addr, + RefType.DATA, SourceType.USER_DEFINED, 0); } finally { program.endTransaction(transactionID, true); @@ -189,8 +185,9 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest { CodeUnit cu = program.getListing().getCodeUnitAt(addr); cu.setCommentAsArray(CodeUnit.PLATE_COMMENT, new String[] { originalText }); // create a reference to addr - program.getReferenceManager().addMemoryReference(getAddr(0x010023ee), addr, - RefType.DATA, SourceType.USER_DEFINED, 0); + program.getReferenceManager() + .addMemoryReference(getAddr(0x010023ee), addr, + RefType.DATA, SourceType.USER_DEFINED, 0); } finally { program.endTransaction(transactionID, true); @@ -247,7 +244,7 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest { public void testShowTransitionPlates() throws Exception { // no plate comment - assertTrue(!cb.goToField(getAddr(0x1001100), PlateFieldFactory.FIELD_NAME, 1, 1)); + assertFalse(cb.goToField(getAddr(0x1001100), PlateFieldFactory.FIELD_NAME, 1, 1)); setBooleanOption(PlateFieldFactory.SHOW_TRANSITION_PLATES_OPTION, true); @@ -282,7 +279,7 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest { public void testShowSubroutinePlates() throws Exception { // no subroutine plate comment - assertTrue(!cb.goToField(getAddr(0x1001200), PlateFieldFactory.FIELD_NAME, 1, 1)); + assertFalse(cb.goToField(getAddr(0x1001200), PlateFieldFactory.FIELD_NAME, 1, 1)); setBooleanOption(PlateFieldFactory.SHOW_SUBROUTINE_PLATES_OPTION, true); @@ -297,28 +294,13 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testLinesBeforeFunction() throws Exception { - assertTrue(!cb.goToField(getAddr(0x1001300), PlateFieldFactory.FIELD_NAME, 1, 1)); + assertFalse(cb.goToField(getAddr(0x1001300), PlateFieldFactory.FIELD_NAME, 1, 1)); setIntOption(PlateFieldFactory.LINES_BEFORE_FUNCTIONS_OPTION, 2); assertTrue(cb.goToField(getAddr(0x1001300), PlateFieldFactory.FIELD_NAME, 1, 1)); ListingTextField tf = (ListingTextField) cb.getCurrentField(); assertEquals(2, tf.getNumRows()); -// list.add(cu.getMinAddress()); -// -// ArrayList
list = new ArrayList
(); -// Listing listing = program.getListing(); -// -// FunctionIterator iter = listing.getFunctions(true); -// while (iter.hasNext()) { -// Function f = iter.next(); -// CodeUnit cu = listing.getCodeUnitAt(f.getEntryPoint()); -// assertTrue(cbPlugin.goToField(cu.getMinAddress(), PlateFieldFactory.FIELD_NAME, 1, 1)); -// ListingTextField tf = (ListingTextField) cbPlugin.getCurrentField(); -// assertEquals(5, tf.getNumRows()); -// list.add(cu.getMinAddress()); -// } -// assertEquals(66, list.size()); } @Test @@ -354,7 +336,7 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest { Listing listing = program.getListing(); CodeUnit cu = listing.getCodeUnitAt(getAddr(0x1001500)); - assertTrue(!cb.goToField(cu.getMinAddress(), PlateFieldFactory.FIELD_NAME, 1, 1)); + assertFalse(cb.goToField(cu.getMinAddress(), PlateFieldFactory.FIELD_NAME, 1, 1)); createPlateComment(cu, "This is a plate comment"); @@ -465,8 +447,9 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest { int transactionID = program.startTransaction("test"); CodeUnit cu = program.getListing().getCodeUnitAt(addr); try { - program.getSymbolTable().createLabel(addr, testName.getMethodName(), - SourceType.USER_DEFINED); + program.getSymbolTable() + .createLabel(addr, testName.getMethodName(), + SourceType.USER_DEFINED); cu.setComment(CodeUnit.PLATE_COMMENT, "this is a comment\ngo to the address 0x010028de"); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ClippingTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ClippingTextField.java index b18e84ff03..18ce8acff7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ClippingTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ClippingTextField.java @@ -39,6 +39,7 @@ public class ClippingTextField implements TextField { protected int startX; private int width; private int preferredWidth; + private int numDataRows; private String fullText; private boolean isClipped; @@ -49,28 +50,40 @@ public class ClippingTextField implements TextField { /** * Constructs a new ClippingTextField that allows the cursor beyond the end - * of the line. This is just a pass through constructor that makes the call: + * of the line. * - *
-	 * this(startX, width, new AttributedString[] { textElement }, hlFactory, true);
-	 * 
- * - * @param startX - * The x position of the field - * @param width - * The width of the field - * @param textElement - * The AttributedStrings to display in the field. - * @param hlFactory - * The HighlightFactory object used to paint highlights. + * @param startX The x position of the field + * @param width The width of the field + * @param textElement The AttributedStrings to display in the field. + * @param hlFactory The HighlightFactory object used to paint highlights. */ public ClippingTextField(int startX, int width, FieldElement textElement, HighlightFactory hlFactory) { + // default to one row + this(startX, width, textElement, 1, hlFactory); + } + + /** + * Constructs a new ClippingTextField that allows the cursor beyond the end + * of the line. + * + *

This constructor allows clients to specify the number of data rows that have been + * converted into a single screen row. + * + * @param startX The x position of the field + * @param width The width of the field + * @param textElement The AttributedStrings to display in the field. + * @param numDataRows the number of data rows represented by this single screen row field + * @param hlFactory The HighlightFactory object used to paint highlights. + */ + public ClippingTextField(int startX, int width, FieldElement textElement, int numDataRows, + HighlightFactory hlFactory) { - this.textElement = textElement; - this.hlFactory = hlFactory; this.startX = startX; this.width = width; + this.numDataRows = numDataRows; + this.textElement = textElement; + this.hlFactory = hlFactory; this.preferredWidth = textElement.getStringWidth(); clip(width); @@ -139,7 +152,7 @@ public class ClippingTextField implements TextField { @Override public int getNumDataRows() { - return 1; + return numDataRows; } @Override @@ -336,18 +349,13 @@ public class ClippingTextField implements TextField { @Override public RowColLocation screenToDataLocation(int screenRow, int screenColumn) { return originalElement.getDataLocationForCharacterIndex(screenColumn); - } @Override public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) { int column = textElement.getCharacterIndexForDataLocation(dataRow, dataColumn); if (column < 0) { - // place at the end if past the end - if (dataColumn >= textElement.length()) { - return new DefaultRowColLocation(0, textElement.length()); - } - return new DefaultRowColLocation(); + return new DefaultRowColLocation(0, textElement.length()); } return new RowColLocation(0, column); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeFieldElement.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeFieldElement.java index 38c56c63a1..80a557582e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeFieldElement.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeFieldElement.java @@ -22,6 +22,7 @@ import java.util.List; import javax.swing.JComponent; import docking.widgets.fieldpanel.support.RowColLocation; +import generic.json.Json; /** * A FieldElement that is composed of other FieldElements. The elements are laid out horizontally. @@ -107,46 +108,6 @@ public class CompositeFieldElement implements FieldElement { return heightBelow; } -//================================================================================================== -// FontMetrics methods -//================================================================================================== - - @Override - public int getStringWidth() { - if (textWidth == -1) { - textWidth = 0; - for (FieldElement fieldElement : fieldElements) { - textWidth += fieldElement.getStringWidth(); - } - } - return textWidth; - } - - @Override - public String getText() { - if (fullText == null) { - StringBuilder buffer = new StringBuilder(); - for (FieldElement fieldElement : fieldElements) { - buffer.append(fieldElement.getText()); - } - fullText = buffer.toString(); - } - return fullText; - } - -//================================================================================================== -// Paint methods -//================================================================================================== - - @Override - public void paint(JComponent c, Graphics g, int x, int y) { - int xPos = x; - for (FieldElement fieldElement : fieldElements) { - fieldElement.paint(c, g, xPos, y); - xPos += fieldElement.getStringWidth(); - } - } - @Override public FieldElement replaceAll(char[] targets, char repacement) { FieldElement[] newStrings = new FieldElement[fieldElements.length]; @@ -191,16 +152,6 @@ public class CompositeFieldElement implements FieldElement { return new CompositeFieldElement(newStrings); } - private static class IndexedOffset { - int index; - int offset; - - IndexedOffset(int index, int offset) { - this.index = index; - this.offset = offset; - } - } - @Override public FieldElement getFieldElement(int column) { IndexedOffset startPos = getIndexedOffsetForCharPosition(column); @@ -217,6 +168,46 @@ public class CompositeFieldElement implements FieldElement { return getText(); } + @Override + public int getStringWidth() { + if (textWidth == -1) { + textWidth = 0; + for (FieldElement fieldElement : fieldElements) { + textWidth += fieldElement.getStringWidth(); + } + } + return textWidth; + } + + @Override + public String getText() { + if (fullText == null) { + StringBuilder buffer = new StringBuilder(); + for (FieldElement fieldElement : fieldElements) { + buffer.append(fieldElement.getText()); + } + fullText = buffer.toString(); + } + return fullText; + } + + @Override + public void paint(JComponent c, Graphics g, int x, int y) { + int xPos = x; + for (FieldElement fieldElement : fieldElements) { + fieldElement.paint(c, g, xPos, y); + xPos += fieldElement.getStringWidth(); + } + } + + /** + * Returns the number of sub-elements contained in this field + * @return the number of sub-elements contained in this field + */ + public int getNumElements() { + return fieldElements.length; + } + //================================================================================================== // Location Info //================================================================================================== @@ -229,6 +220,7 @@ public class CompositeFieldElement implements FieldElement { @Override public int getCharacterIndexForDataLocation(int dataRow, int dataColumn) { + int columnsSoFar = 0; for (int i = fieldElements.length - 1; i >= 0; i--) { columnsSoFar += fieldElements[i].length(); @@ -242,4 +234,20 @@ public class CompositeFieldElement implements FieldElement { return -1; } + + private static class IndexedOffset { + int index; + int offset; + + IndexedOffset(int index, int offset) { + this.index = index; + this.offset = offset; + } + + @Override + public String toString() { + return Json.toString(this); + } + } + } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextField.java index 9bdd9ba668..5b6cf22580 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextField.java @@ -342,12 +342,15 @@ public class CompositeVerticalLayoutTextField implements TextField { } private FieldRow getFieldRowFromDataRow(int dataRow) { + int currentRow = 0; for (FieldRow row : fieldRows) { - if (currentRow >= dataRow) { + int length = row.field.getNumDataRows(); + + if (currentRow + length > dataRow) { return row; } - currentRow += row.field.getNumDataRows(); + currentRow += length; } return fieldRows.get(fieldRows.size() - 1); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FlowLayoutTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FlowLayoutTextField.java index 6b6d992fa9..6aa571491c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FlowLayoutTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FlowLayoutTextField.java @@ -20,8 +20,9 @@ import java.util.*; import docking.widgets.fieldpanel.support.HighlightFactory; /** - * This class provides a TextField implementation that takes multiple AttributedStrings and places - * as many that will fit on a line without clipping before continuing to the next line. + * This class provides a TextField implementation that takes multiple AttributedString field + * elements and places as many that will fit on a line without clipping before continuing to the + * next line. */ public class FlowLayoutTextField extends VerticalLayoutTextField { @@ -67,27 +68,35 @@ public class FlowLayoutTextField extends VerticalLayoutTextField { int currentIndex = 0; while (currentIndex < elements.size()) { int numberPerLine = getNumberOfElementsPerLine(elements, currentIndex, width); - subFields.add(createLine(elements, currentIndex, numberPerLine)); + subFields.add(createLineFromElements(elements, currentIndex, numberPerLine)); currentIndex += numberPerLine; } return subFields; } - private static CompositeFieldElement createLine(List elements, int from, - int length) { - return new CompositeFieldElement(elements.subList(from, from + length)); + @Override + protected TextField createFieldForLine(FieldElement element) { + CompositeFieldElement composite = (CompositeFieldElement) element; + int numDataRows = composite.getNumElements(); + return new ClippingTextField(startX, width, element, numDataRows, hlFactory); + } + + private static CompositeFieldElement createLineFromElements(List elements, + int start, int length) { + return new CompositeFieldElement(elements.subList(start, start + length)); } private static int getNumberOfElementsPerLine(List elements, int start, int width) { + int currentWidth = 0; - int count = 0; - for (FieldElement element : elements) { + for (int i = start; i < elements.size(); i++) { + FieldElement element = elements.get(i); currentWidth += element.getStringWidth(); - count++; if (currentWidth > width) { - return Math.max(count - 1, 1); + int count = i - start; + return Math.max(count, 1); } } return elements.size() - start; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/VerticalLayoutTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/VerticalLayoutTextField.java index 80b3b6ade0..06edbddfc4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/VerticalLayoutTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/VerticalLayoutTextField.java @@ -16,7 +16,8 @@ package docking.widgets.fieldpanel.field; import java.awt.*; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.swing.JComponent; @@ -26,6 +27,7 @@ import org.apache.commons.lang3.StringUtils; import docking.widgets.fieldpanel.internal.FieldBackgroundColorManager; import docking.widgets.fieldpanel.internal.PaintContext; import docking.widgets.fieldpanel.support.*; +import generic.json.Json; /** * This class provides a TextField implementation that takes multiple FieldElements and places @@ -33,7 +35,10 @@ import docking.widgets.fieldpanel.support.*; */ public class VerticalLayoutTextField implements TextField { - protected List subFields; // list of fields for FieldElements + // + // The sub-fields of this text field. Each FieldRow has a Field, a screen row and a data row. + // + protected List subFields; protected int startX; protected int width; protected int preferredWidth; @@ -136,16 +141,16 @@ public class VerticalLayoutTextField implements TextField { } protected void calculateHeight() { - heightAbove = (subFields.get(0)).getHeightAbove(); - for (Field field : subFields) { - height += field.getHeight(); + heightAbove = (subFields.get(0)).field.getHeightAbove(); + for (FieldRow fieldRow : subFields) { + height += fieldRow.field.getHeight(); } } private int calculatePreferredWidth() { int widest = 0; - for (Field field : subFields) { - widest = Math.max(widest, field.getPreferredWidth()); + for (FieldRow fieldRow : subFields) { + widest = Math.max(widest, fieldRow.field.getPreferredWidth()); } return widest; } @@ -197,20 +202,21 @@ public class VerticalLayoutTextField implements TextField { @Override public int getNumCols(int row) { - Field f = subFields.get(row); + Field f = getField(row); return f.getNumCols(0); } @Override public int getRow(int y) { - if (y < 0) { + if (y < -heightAbove) { return 0; } - int heightSoFar = 0; + int heightSoFar = -heightAbove; + int n = subFields.size(); for (int i = 0; i < n; i++) { - Field f = subFields.get(i); + Field f = getField(i); heightSoFar += f.getHeight(); if (heightSoFar > y) { return i; @@ -221,7 +227,7 @@ public class VerticalLayoutTextField implements TextField { @Override public int getCol(int row, int x) { - Field f = subFields.get(row); + Field f = getField(row); return f.getCol(0, x); } @@ -230,7 +236,7 @@ public class VerticalLayoutTextField implements TextField { int y = -heightAbove; for (int i = 0; i < row; i++) { - Field f = subFields.get(row); + Field f = getField(i); y += f.getHeight(); } return y; @@ -238,7 +244,7 @@ public class VerticalLayoutTextField implements TextField { @Override public int getX(int row, int col) { - Field f = subFields.get(row); + Field f = getField(row); return f.getX(0, col); } @@ -248,7 +254,7 @@ public class VerticalLayoutTextField implements TextField { if ((row < 0) || (row >= subFields.size())) { return false; } - Field f = subFields.get(row); + Field f = getField(row); return f.isValid(0, col); } @@ -295,7 +301,7 @@ public class VerticalLayoutTextField implements TextField { int translatedY = 0; int extraSpace = rowSeparator.length(); for (int i = 0; i < n; i++) { - ClippingTextField subField = (ClippingTextField) subFields.get(i); + ClippingTextField subField = (ClippingTextField) getField(i); int subFieldHeight = subField.getHeight(); int endY = startY + subFieldHeight; @@ -332,7 +338,7 @@ public class VerticalLayoutTextField implements TextField { private void print(Graphics g, PaintContext context) { int n = subFields.size(); for (int i = 0; i < n; i++) { - ClippingTextField clippingField = (ClippingTextField) subFields.get(i); + ClippingTextField clippingField = (ClippingTextField) getField(i); clippingField.print(g, context); @@ -346,10 +352,10 @@ public class VerticalLayoutTextField implements TextField { if ((row < 0) || (row >= subFields.size())) { return null; } - Field f = subFields.get(row); + Field f = getField(row); Rectangle r = f.getCursorBounds(0, col); for (int i = 0; i < row; i++) { - f = subFields.get(row); + f = getField(i); r.y += f.getHeight(); } return r; @@ -370,10 +376,11 @@ public class VerticalLayoutTextField implements TextField { if ((topOfScreen < -heightAbove) || (topOfScreen > height - heightAbove)) { return max; } + int row = getRow(topOfScreen); int y = getY(row); int rowOffset = topOfScreen - y; - int rowHeight = (subFields.get(row)).getHeight(); + int rowHeight = getField(row).getHeight(); if (direction > 0) { // if scrolling down return rowHeight - rowOffset; } @@ -399,14 +406,6 @@ public class VerticalLayoutTextField implements TextField { isPrimary = state; } - /** - * Returns the list of subfields in this field. - * @return the list of subfields in this field. - */ - public List getSubfields() { - return Collections.unmodifiableList(subFields); - } - @Override public int getHeightAbove() { return heightAbove; @@ -425,17 +424,17 @@ public class VerticalLayoutTextField implements TextField { @Override public FieldElement getFieldElement(int screenRow, int screenColumn) { - TextField f = subFields.get(screenRow); + TextField f = getField(screenRow); int fieldRow = 0; // each field is on a single row return f.getFieldElement(fieldRow, screenColumn); } - protected List layoutElements(List textElements, int maxLines) { - List newSubFields = new ArrayList<>(); + protected List layoutElements(List textElements, int maxLines) { + List newSubFields = new ArrayList<>(); boolean tooManyLines = textElements.size() > maxLines; - + int currentRow = 0; for (int i = 0; i < textElements.size() && i < maxLines; i++) { FieldElement element = textElements.get(i); if (tooManyLines && (i == maxLines - 1)) { @@ -444,9 +443,13 @@ public class VerticalLayoutTextField implements TextField { elements[1] = new StrutFieldElement(500); element = new CompositeFieldElement(elements); } - TextField field = new ClippingTextField(startX, width, element, hlFactory); - newSubFields.add(field); + TextField field = createFieldForLine(element); + int modelRow = currentRow; + int screenRow = newSubFields.size(); + newSubFields.add(new FieldRow(field, modelRow, screenRow)); isClipped |= field.isClipped(); + + currentRow += field.getNumRows(); } isClipped |= tooManyLines; @@ -455,38 +458,36 @@ public class VerticalLayoutTextField implements TextField { } /** - * Translates the row and column to a String index and character offset into - * that string. - * @param screenRow the row containing the location. - * @param screenColumn the character position in the row of the location - * @return a MultiStringLocation containing the string index and position - * within that string. + * Create the text field for given field element + * @param element the element + * @return the field */ + protected TextField createFieldForLine(FieldElement element) { + return new ClippingTextField(startX, width, element, hlFactory); + } + @Override public RowColLocation screenToDataLocation(int screenRow, int screenColumn) { screenRow = Math.min(screenRow, subFields.size() - 1); screenRow = Math.max(screenRow, 0); - TextField field = subFields.get(screenRow); + TextField field = getField(screenRow); screenColumn = Math.min(screenColumn, field.getText().length()); screenColumn = Math.max(screenColumn, 0); - int fieldRow = 0; // each field is on a single row - return field.screenToDataLocation(fieldRow, screenColumn); + int dataRow = getDataRow(field); + return field.screenToDataLocation(dataRow, screenColumn); } @Override public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) { - if (dataRow >= getNumRows()) { - TextField lastField = subFields.get(subFields.size()); - return new DefaultRowColLocation(lastField.getText().length(), subFields.size() - 1); - } - - TextField field = subFields.get(dataRow); + FieldRow fieldRow = getFieldRowFromDataRow(dataRow); + TextField field = fieldRow.field; RowColLocation location = field.dataToScreenLocation(dataRow, dataColumn); - return location.withRow(dataRow); + int screenRow = fieldRow.screenRow; + return location.withRow(screenRow); } @Override @@ -517,7 +518,8 @@ public class VerticalLayoutTextField implements TextField { } int lastRow = n - 1; - int lastColumn = subFields.get(lastRow).getText().length(); + TextField field = getField(lastRow); + int lastColumn = field.getText().length(); return new DefaultRowColLocation(lastRow, lastColumn); } @@ -525,4 +527,54 @@ public class VerticalLayoutTextField implements TextField { public boolean isClipped() { return isClipped; } + + /** + * Returns the view's text lines of this field + * @return the lines + */ + protected List getLines() { + return lines; + } + + private TextField getField(int screenRow) { + return subFields.get(screenRow).field; + } + + private FieldRow getFieldRowFromDataRow(int dataRow) { + int currentRow = 0; + for (FieldRow row : subFields) { + int length = row.field.getNumDataRows(); + if (currentRow + length > dataRow) { + return row; + } + currentRow += length; + } + return subFields.get(subFields.size() - 1); + } + + private int getDataRow(TextField field) { + for (FieldRow fieldRow : subFields) { + if (fieldRow.field == field) { + return fieldRow.dataRow; + } + } + return 0; + } + + private class FieldRow { + private TextField field; + private int dataRow; + private int screenRow; + + FieldRow(TextField field, int dataRow, int screenRow) { + this.field = field; + this.dataRow = dataRow; + this.screenRow = screenRow; + } + + @Override + public String toString() { + return Json.toString(this); + } + } } diff --git a/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/FlowLayoutTextFieldTest.java b/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/FlowLayoutTextFieldTest.java index 31682df7e7..bdd3486d6a 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/FlowLayoutTextFieldTest.java +++ b/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/FlowLayoutTextFieldTest.java @@ -47,6 +47,22 @@ public class FlowLayoutTextFieldTest extends AbstractGenericTest { FontMetrics fm = tk.getFontMetrics(font); List elements = new ArrayList<>(); + /* + Data Rows: + + Hello + World + Supercalifragilisticexpialidocious + Wow! + + + Screen Rows: + + Hello World + Supercalifra... + Wow + */ + elements.add(new TextFieldElement(new AttributedString("Hello ", Color.BLUE, fm), 0, 0)); elements.add(new TextFieldElement( new AttributedString("World ", Color.RED, fm, true, Color.BLUE), 1, 0)); @@ -95,17 +111,19 @@ public class FlowLayoutTextFieldTest extends AbstractGenericTest { assertEquals(new RowColLocation(1, 0), textField.dataToScreenLocation(2, 0)); assertEquals(new RowColLocation(1, 4), textField.dataToScreenLocation(2, 4)); - assertEquals(new RowColLocation(1, 15), textField.dataToScreenLocation(2, 15)); + + // Supercalifra (12 chars); ... (3 chars); Supercalifra... (15 chars) + assertEquals(new DefaultRowColLocation(1, 12), textField.dataToScreenLocation(2, 15)); assertEquals(new RowColLocation(2, 0), textField.dataToScreenLocation(3, 0)); assertEquals(new RowColLocation(2, 4), textField.dataToScreenLocation(3, 4)); - assertEquals(new RowColLocation(0, 0), textField.dataToScreenLocation(0, 12)); - assertEquals(new RowColLocation(0, 0), textField.dataToScreenLocation(0, 75)); + assertEquals(new DefaultRowColLocation(0, 12), textField.dataToScreenLocation(0, 12)); + assertEquals(new DefaultRowColLocation(0, 12), textField.dataToScreenLocation(0, 75)); } @Test - public void testGetRowColumn() { + public void testTextOffsetToScreenLocation() { assertEquals(new RowColLocation(0, 0), textField.textOffsetToScreenLocation(0)); assertEquals(new RowColLocation(0, 5), textField.textOffsetToScreenLocation(5)); assertEquals(new RowColLocation(0, 6), textField.textOffsetToScreenLocation(6)); @@ -116,7 +134,8 @@ public class FlowLayoutTextFieldTest extends AbstractGenericTest { assertEquals(new RowColLocation(1, 3), textField.textOffsetToScreenLocation(15)); assertEquals(new RowColLocation(1, 18), textField.textOffsetToScreenLocation(30)); + assertEquals(new RowColLocation(2, 0), textField.textOffsetToScreenLocation(47)); - assertEquals(new RowColLocation(2, 5), textField.textOffsetToScreenLocation(1000)); + assertEquals(new DefaultRowColLocation(2, 5), textField.textOffsetToScreenLocation(1000)); } } diff --git a/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextFieldTest.java b/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextFieldTest.java index 4f9f8c51b8..a010350c23 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextFieldTest.java +++ b/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextFieldTest.java @@ -324,7 +324,7 @@ public class CompositeVerticalLayoutTextFieldTest extends AbstractGenericTest { assertRowCol(1, 5, field.dataToScreenLocation(1, 5)); // try accessing clipped rows - assertRowCol(1, 0, field.dataToScreenLocation(2, 0)); + assertRowCol(1, 5, field.dataToScreenLocation(2, 0)); assertRowCol(1, 5, field.dataToScreenLocation(2, 5)); assertRowCol(1, 5, field.dataToScreenLocation(20, 50)); } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java index cabcaa6b2e..1f131a3424 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java @@ -435,7 +435,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { String operandPrefix = "dword ptr [EBP + "; String operandReferenceName = "destStr]"; OperandFieldLocation variableOperandReferenceLocation = new OperandFieldLocation(program, - addr("0100416c"), null, addr("0x8"), operandPrefix + operandReferenceName, 1, 9); + addr("0100416c"), null, addr("0x8"), operandPrefix + operandReferenceName, 0, 9); codeBrowserPlugin.goTo(variableOperandReferenceLocation); DockingAction pasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME); @@ -1551,8 +1551,8 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { } /* - * We remove the FieldPanel focus listeners for these tests, as when they lose focus, - * the selection mechanism does not work as expected. Focus changes can happen + * We remove the FieldPanel focus listeners for these tests, as when they lose focus, + * the selection mechanism does not work as expected. Focus changes can happen * indeterminately during parallel batch testing. */ private void removeFieldPanelFocusListeners(Container c) {