mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-10-02 16:33:49 +00:00
Merge remote-tracking branch 'origin/GP-0-dragonmacher-xref-merge-test-fixes--SQUASHED'
This commit is contained in:
commit
e3f5e9a061
|
@ -60,7 +60,8 @@ public class CommentFieldSearcher extends ProgramDatabaseFieldSearcher {
|
||||||
return nextAddress;
|
return nextAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void findMatchesForCurrentAddress(Address address, List<ProgramLocation> currentMatches) {
|
private void findMatchesForCurrentAddress(Address address,
|
||||||
|
List<ProgramLocation> currentMatches) {
|
||||||
String comment = program.getListing().getComment(commentType, address);
|
String comment = program.getListing().getComment(commentType, address);
|
||||||
if (comment == null) {
|
if (comment == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -84,7 +85,7 @@ public class CommentFieldSearcher extends ProgramDatabaseFieldSearcher {
|
||||||
charOffset, rowIndex);
|
charOffset, rowIndex);
|
||||||
case CodeUnit.PLATE_COMMENT:
|
case CodeUnit.PLATE_COMMENT:
|
||||||
return new PlateFieldLocation(program, address, dataPath, rowIndex, charOffset,
|
return new PlateFieldLocation(program, address, dataPath, rowIndex, charOffset,
|
||||||
comments, rowIndex - 1);
|
comments, rowIndex);
|
||||||
case CodeUnit.REPEATABLE_COMMENT:
|
case CodeUnit.REPEATABLE_COMMENT:
|
||||||
return new RepeatableCommentFieldLocation(program, address, dataPath, comments,
|
return new RepeatableCommentFieldLocation(program, address, dataPath, comments,
|
||||||
rowIndex, charOffset, rowIndex); // TODO One of searchStrIndex parameters is wrong.
|
rowIndex, charOffset, rowIndex); // TODO One of searchStrIndex parameters is wrong.
|
||||||
|
|
|
@ -427,11 +427,9 @@ public class BytesFieldFactory extends FieldFactory {
|
||||||
|
|
||||||
ListingTextField btf = (ListingTextField) bf;
|
ListingTextField btf = (ListingTextField) bf;
|
||||||
RowColLocation rcl = btf.dataToScreenLocation(tokenIndex, tokenOffset);
|
RowColLocation rcl = btf.dataToScreenLocation(tokenIndex, tokenOffset);
|
||||||
|
|
||||||
if (hasSamePath(bf, loc)) {
|
if (hasSamePath(bf, loc)) {
|
||||||
return new FieldLocation(index, fieldNum, rcl.row(), rcl.col());
|
return new FieldLocation(index, fieldNum, rcl.row(), rcl.col());
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import docking.widgets.fieldpanel.field.*;
|
import docking.widgets.fieldpanel.field.*;
|
||||||
import docking.widgets.fieldpanel.support.*;
|
import docking.widgets.fieldpanel.support.*;
|
||||||
import ghidra.app.util.HelpTopics;
|
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.Options;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressOverflowException;
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.symbol.*;
|
import ghidra.program.model.symbol.*;
|
||||||
import ghidra.program.util.*;
|
import ghidra.program.util.*;
|
||||||
|
@ -141,21 +144,18 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeUnit cu = (CodeUnit) proxy.getObject();
|
CodeUnit cu = (CodeUnit) proxy.getObject();
|
||||||
List<FieldElement> elements = new ArrayList<>(10);
|
|
||||||
boolean isClipped = false;
|
boolean isClipped = false;
|
||||||
|
List<FieldElement> elements = new ArrayList<>();
|
||||||
String commentText = getCommentText(cu);
|
String commentText = getCommentText(cu);
|
||||||
if ((commentText == null) || (commentText.isEmpty())) {
|
if (StringUtils.isBlank(commentText)) {
|
||||||
generateDefaultPlate(elements, cu);
|
getDefaultFieldElements(cu, elements);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
isClipped = generateFormattedPlateComment(elements, cu);
|
isClipped = getFormattedFieldElements(cu, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
addBlankLines(elements, cu);
|
if (elements.isEmpty()) {
|
||||||
|
return null; // no real or default comments
|
||||||
if (elements.size() == 0) {
|
|
||||||
// no real or default comment
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNestedDataAtSameAddressAsParent(proxy)) {
|
if (isNestedDataAtSameAddressAsParent(proxy)) {
|
||||||
|
@ -170,6 +170,27 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
return new PlateListingTextField(proxy, textField);
|
return new PlateListingTextField(proxy, textField);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean getFormattedFieldElements(CodeUnit cu, List<FieldElement> 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<FieldElement> 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) {
|
private boolean isNestedDataAtSameAddressAsParent(ProxyObj<?> proxy) {
|
||||||
if (proxy instanceof DataProxy) {
|
if (proxy instanceof DataProxy) {
|
||||||
DataProxy dp = (DataProxy) proxy;
|
DataProxy dp = (DataProxy) proxy;
|
||||||
|
@ -200,67 +221,39 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
return buffy.toString();
|
return buffy.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Creates desired FieldElements and puts them in the given list. Returns true if any of the
|
* 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.
|
* 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<FieldElement> elementList, CodeUnit cu) {
|
private boolean generateFormattedPlateComment(List<FieldElement> elements, String[] comments,
|
||||||
String[] comments = cu.getCommentAsArray(CodeUnit.PLATE_COMMENT);
|
Program p) {
|
||||||
if ((comments == null) || (comments.length == 0)) {
|
if (comments == null || comments.length == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Program program = cu.getProgram();
|
|
||||||
AttributedString prototype = new AttributedString(EMPTY_STRING, color, getMetrics());
|
AttributedString prototype = new AttributedString(EMPTY_STRING, color, getMetrics());
|
||||||
|
|
||||||
for (int i = 0; i < comments.length; i++) {
|
AttributedString asteriscs = getStarsString();
|
||||||
elementList
|
int row = elements.size();
|
||||||
.add(CommentUtils.parseTextForAnnotations(comments[i], program, prototype, i));
|
|
||||||
|
// 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) {
|
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<FieldElement> elementList, CodeUnit cu) {
|
// add bottom border
|
||||||
AttributedString prototype = new AttributedString(EMPTY_STRING, color, getMetrics());
|
elements.add(new TextFieldElement(asteriscs, row++, 0));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<FieldElement> 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;
|
return isClipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,13 +262,17 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
* Text will be left justified between two '*' and padded based upon the
|
* Text will be left justified between two '*' and padded based upon the
|
||||||
* available field width.
|
* available field width.
|
||||||
* @param elements the field elements that may get updated
|
* @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
|
* @return formatted plate text line
|
||||||
*/
|
*/
|
||||||
private boolean addSideBorders(List<FieldElement> elements) {
|
private boolean addSideBorders(List<FieldElement> elements, int commentsStart) {
|
||||||
boolean isClipped = false;
|
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);
|
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);
|
FieldElementResult result = addSideBorder(element.substring(1), 1, true);
|
||||||
isClipped = result.isClipped();
|
isClipped = result.isClipped();
|
||||||
elements.set(0, result.getFieldElement());
|
elements.set(0, result.getFieldElement());
|
||||||
|
@ -283,8 +280,8 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < elements.size(); i++) {
|
for (int i = commentsStart; i < elements.size(); i++) {
|
||||||
FieldElementResult result = addSideBorder(elements.get(i), i + 1, false);
|
FieldElementResult result = addSideBorder(elements.get(i), i, false);
|
||||||
isClipped |= result.isClipped();
|
isClipped |= result.isClipped();
|
||||||
elements.set(i, result.getFieldElement());
|
elements.set(i, result.getFieldElement());
|
||||||
}
|
}
|
||||||
|
@ -311,7 +308,7 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
int prePadding = center ? totalPadding / 2 : 0;
|
int prePadding = center ? totalPadding / 2 : 0;
|
||||||
int postPadding = center ? (totalPadding + 1) / 2 : totalPadding;
|
int postPadding = center ? (totalPadding + 1) / 2 : totalPadding;
|
||||||
|
|
||||||
StringBuffer buffy = new StringBuffer();
|
StringBuilder buffy = new StringBuilder();
|
||||||
buffy.append('*').append(' ');
|
buffy.append('*').append(' ');
|
||||||
addPadding(buffy, prePadding);
|
addPadding(buffy, prePadding);
|
||||||
|
|
||||||
|
@ -335,21 +332,56 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
ellipsisLength > 0);
|
ellipsisLength > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPadding(StringBuffer buf, int count) {
|
private void addPadding(StringBuilder buf, int count) {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
buf.append(' ');
|
buf.append(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateDefaultPlate(List<FieldElement> elementList, CodeUnit cu) {
|
private void addBlankLines(List<FieldElement> elements, int numberBlankLines, CodeUnit cu) {
|
||||||
String defaultComment = getDefaultComment(cu);
|
AttributedString prototype = new AttributedString(EMPTY_STRING, color, getMetrics());
|
||||||
if (defaultComment != null) {
|
for (int row = 0; row < numberBlankLines; row++) {
|
||||||
AttributedString as = new AttributedString(defaultComment, color, getMetrics());
|
elements.add(new TextFieldElement(prototype, row, 0));
|
||||||
elementList.add(new TextFieldElement(as, 0, 0));
|
|
||||||
addBorder(elementList);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<FieldElement> 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) {
|
private String getDefaultComment(CodeUnit cu) {
|
||||||
|
|
||||||
if (showFunctionPlates) {
|
if (showFunctionPlates) {
|
||||||
|
@ -367,11 +399,6 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
return SUBROUTINE_PLATE_COMMENT;
|
return SUBROUTINE_PLATE_COMMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle case statements?
|
|
||||||
// if (showCasePlates) {
|
|
||||||
// return CASE_PLATE_COMMENT;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (showTransitionPlates) {
|
if (showTransitionPlates) {
|
||||||
if (isDeadCode(cu)) {
|
if (isDeadCode(cu)) {
|
||||||
return DEAD_CODE_PLATE_COMMENT;
|
return DEAD_CODE_PLATE_COMMENT;
|
||||||
|
@ -432,7 +459,7 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
int n = width / starWidth;
|
int n = width / starWidth;
|
||||||
|
|
||||||
if (stars.length() != n) {
|
if (stars.length() != n) {
|
||||||
StringBuffer buf = new StringBuffer();
|
StringBuilder buf = new StringBuilder();
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
buf.append('*');
|
buf.append('*');
|
||||||
}
|
}
|
||||||
|
@ -465,18 +492,18 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
((ListingTextField) listingField).screenToDataLocation(row, col);
|
((ListingTextField) listingField).screenToDataLocation(row, col);
|
||||||
|
|
||||||
//
|
//
|
||||||
// The 'row' value is an offset from the top of the plate comment, which has 0 or
|
// The 'row' value includes blank lines and header decoration lines. The 'commentRow' used
|
||||||
// more blank lines at the top, followed by a line of asterisks.
|
// below is the index into the list of comments. Calculate the comment beginning by
|
||||||
|
// removing the non-comment lines.
|
||||||
//
|
//
|
||||||
int fillerLineCount = getNumberOfLeadingFillerLines(listingField);
|
int fillerLineCount = getNumberOfLeadingFillerLines(listingField);
|
||||||
|
|
||||||
int commentRow = row - fillerLineCount;
|
int commentRow = row - fillerLineCount;
|
||||||
if (commentRow >= comments.length) {
|
if (commentRow >= comments.length || commentRow < 0) {
|
||||||
commentRow = -1; // clicked the bottom decoration line
|
commentRow = -1; // clicked above the comment or the bottom decoration line
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PlateFieldLocation(cu.getProgram(), ((CodeUnit) proxyObject).getMinAddress(),
|
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) {
|
private int getNumberOfLeadingFillerLines(ListingField listingField) {
|
||||||
|
@ -491,6 +518,7 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
@Override
|
@Override
|
||||||
public FieldLocation getFieldLocation(ListingField listingField, BigInteger index, int fieldNum,
|
public FieldLocation getFieldLocation(ListingField listingField, BigInteger index, int fieldNum,
|
||||||
ProgramLocation programLoc) {
|
ProgramLocation programLoc) {
|
||||||
|
|
||||||
if (!(programLoc instanceof CommentFieldLocation)) {
|
if (!(programLoc instanceof CommentFieldLocation)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -507,8 +535,40 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
return null;
|
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;
|
ListingTextField listingTextField = (ListingTextField) listingField;
|
||||||
RowColLocation location = listingTextField.dataToScreenLocation(commentLocation.getRow(),
|
RowColLocation location = listingTextField.dataToScreenLocation(dataRow,
|
||||||
commentLocation.getCharOffset());
|
commentLocation.getCharOffset());
|
||||||
return new FieldLocation(index, fieldNum, location.row(), location.col());
|
return new FieldLocation(index, fieldNum, location.row(), location.col());
|
||||||
}
|
}
|
||||||
|
@ -614,7 +674,7 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
Address prevAddr = cu.getMinAddress().subtractNoWrap(1);
|
Address prevAddr = cu.getMinAddress().subtractNoWrap(1);
|
||||||
return cu.getProgram().getListing().getCodeUnitContaining(prevAddr);
|
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!
|
// we are just being lazy and not validating before doing the subtract--SOCK!
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -638,7 +698,7 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
}
|
}
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
StringBuffer sb = new StringBuffer();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("\n");
|
sb.append("\n");
|
||||||
for (int i = 0; i < 19; i++) {
|
for (int i = 0; i < 19; i++) {
|
||||||
sb.append("|");
|
sb.append("|");
|
||||||
|
@ -725,14 +785,13 @@ public class PlateFieldFactory extends FieldFactory {
|
||||||
int getLeadingFillerLineCount() {
|
int getLeadingFillerLineCount() {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
for (Field field : subFields) {
|
for (String line : getLines()) {
|
||||||
String text = field.getText().trim();
|
|
||||||
count++;
|
count++;
|
||||||
if (text.isEmpty()) {
|
if (line.isEmpty()) {
|
||||||
continue; // skip leading blank lines
|
continue; // skip leading blank lines
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text.startsWith("*")) {
|
if (line.startsWith("*")) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -551,7 +551,7 @@ public class XRefFieldFactory extends FieldFactory {
|
||||||
List<XrefFieldElement> elements = new ArrayList<>();
|
List<XrefFieldElement> elements = new ArrayList<>();
|
||||||
FunctionManager functionManager = program.getFunctionManager();
|
FunctionManager functionManager = program.getFunctionManager();
|
||||||
Function currentFunction = functionManager.getFunctionContaining(cu.getMinAddress());
|
Function currentFunction = functionManager.getFunctionContaining(cu.getMinAddress());
|
||||||
int n = tooMany ? maxXRefs + 1 : totalXrefs;
|
int n = tooMany ? maxXRefs : totalXrefs;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (; count < xrefs.size() && count < n; count++) {
|
for (; count < xrefs.size() && count < n; count++) {
|
||||||
Reference ref = xrefs.get(count);
|
Reference ref = xrefs.get(count);
|
||||||
|
|
|
@ -896,7 +896,8 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
assertEquals("XREFs Field.Display Local Block", names.get(1));
|
assertEquals("XREFs Field.Display Local Block", names.get(1));
|
||||||
assertEquals("XREFs Field.Display Namespace", names.get(2));
|
assertEquals("XREFs Field.Display Namespace", names.get(2));
|
||||||
assertEquals("XREFs Field.Display Reference Type", names.get(3));
|
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));
|
assertTrue(cb.goToField(addr("0x1003d9f"), "XRef", 0, 0));
|
||||||
|
|
||||||
|
@ -968,7 +969,9 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
btf = (ListingTextField) cb.getCurrentField();
|
btf = (ListingTextField) cb.getCurrentField();
|
||||||
assertEquals(9, btf.getNumRows());
|
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();
|
cb.updateNow();
|
||||||
assertTrue(cb.goToField(addr("0x1003d9f"), "XRef", 0, 0));
|
assertTrue(cb.goToField(addr("0x1003d9f"), "XRef", 0, 0));
|
||||||
btf = (ListingTextField) cb.getCurrentField();
|
btf = (ListingTextField) cb.getCurrentField();
|
||||||
|
|
|
@ -212,25 +212,25 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest {
|
||||||
for (int i = 0; i < 22; i++) {
|
for (int i = 0; i < 22; i++) {
|
||||||
scrollbar.getUnitIncrement(1);
|
scrollbar.getUnitIncrement(1);
|
||||||
}
|
}
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
assertEquals(addr("0x1001027"), codeBrowser.getAddressTopOfScreen());
|
assertEquals(addr("0x1001027"), codeBrowser.getAddressTopOfScreen());
|
||||||
|
|
||||||
for (int i = 0; i < 18; i++) {
|
for (int i = 0; i < 18; i++) {
|
||||||
scrollbar.getUnitIncrement(-1);
|
scrollbar.getUnitIncrement(-1);
|
||||||
}
|
}
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen());
|
assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen());
|
||||||
|
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
scrollbar.getBlockIncrement(1);
|
scrollbar.getBlockIncrement(1);
|
||||||
}
|
}
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
assertEquals(addr("0x1001136"), codeBrowser.getAddressTopOfScreen());
|
assertEquals(addr("0x1001136"), codeBrowser.getAddressTopOfScreen());
|
||||||
|
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
scrollbar.getBlockIncrement(-1);
|
scrollbar.getBlockIncrement(-1);
|
||||||
}
|
}
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen());
|
assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -333,25 +333,25 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest {
|
||||||
for (int i = 0; i < 20; i++) {
|
for (int i = 0; i < 20; i++) {
|
||||||
scrollbar.getUnitIncrement(1);
|
scrollbar.getUnitIncrement(1);
|
||||||
}
|
}
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
assertEquals(addr("0x1001025"), codeBrowser.getAddressTopOfScreen());
|
assertEquals(addr("0x1001025"), codeBrowser.getAddressTopOfScreen());
|
||||||
|
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
scrollbar.getUnitIncrement(-1);
|
scrollbar.getUnitIncrement(-1);
|
||||||
}
|
}
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen());
|
assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen());
|
||||||
|
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
scrollbar.getBlockIncrement(1);
|
scrollbar.getBlockIncrement(1);
|
||||||
}
|
}
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
assertEquals(addr("0x1001136"), codeBrowser.getAddressTopOfScreen());
|
assertEquals(addr("0x1001136"), codeBrowser.getAddressTopOfScreen());
|
||||||
|
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
scrollbar.getBlockIncrement(-1);
|
scrollbar.getBlockIncrement(-1);
|
||||||
}
|
}
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen());
|
assertEquals(addr("0x1001000"), codeBrowser.getAddressTopOfScreen());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -412,7 +412,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest {
|
||||||
|
|
||||||
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
||||||
click(codeBrowser, 1);
|
click(codeBrowser, 1);
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
|
|
||||||
codeBrowser.goToField(addr("0x1007001"), "Address", 0, 0);
|
codeBrowser.goToField(addr("0x1007001"), "Address", 0, 0);
|
||||||
assertEquals("01007001", codeBrowser.getCurrentFieldText());
|
assertEquals("01007001", codeBrowser.getCurrentFieldText());
|
||||||
|
@ -423,7 +423,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest {
|
||||||
|
|
||||||
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
||||||
click(codeBrowser, 1);
|
click(codeBrowser, 1);
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
|
|
||||||
codeBrowser.goToField(addr("0x1007000"), "Address", 0, 0);
|
codeBrowser.goToField(addr("0x1007000"), "Address", 0, 0);
|
||||||
for (int i = 0; i < 7; i++) {
|
for (int i = 0; i < 7; i++) {
|
||||||
|
@ -450,7 +450,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest {
|
||||||
|
|
||||||
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
||||||
click(codeBrowser, 1);
|
click(codeBrowser, 1);
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
codeBrowser.goToField(addr("0x1007008"), "Address", 0, 0);
|
codeBrowser.goToField(addr("0x1007008"), "Address", 0, 0);
|
||||||
assertEquals("01007008", codeBrowser.getCurrentFieldText());
|
assertEquals("01007008", codeBrowser.getCurrentFieldText());
|
||||||
codeBrowser.goToField(addr("0x1007010"), "Address", 0, 0);
|
codeBrowser.goToField(addr("0x1007010"), "Address", 0, 0);
|
||||||
|
@ -458,7 +458,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest {
|
||||||
|
|
||||||
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
||||||
click(codeBrowser, 1);
|
click(codeBrowser, 1);
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
codeBrowser.goToField(addr("0x1007000"), "Address", 0, 0);
|
codeBrowser.goToField(addr("0x1007000"), "Address", 0, 0);
|
||||||
codeBrowser.goToField(addr("0x1007008"), "Address", 0, 0);
|
codeBrowser.goToField(addr("0x1007008"), "Address", 0, 0);
|
||||||
assertEquals("01007000", codeBrowser.getCurrentFieldText());
|
assertEquals("01007000", codeBrowser.getCurrentFieldText());
|
||||||
|
@ -477,14 +477,14 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest {
|
||||||
|
|
||||||
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
||||||
click(codeBrowser, 1);
|
click(codeBrowser, 1);
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
codeBrowser.goToField(addr("0x1007023"), "+", 0, 0);
|
codeBrowser.goToField(addr("0x1007023"), "+", 0, 0);
|
||||||
click(codeBrowser, 1);
|
click(codeBrowser, 1);
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
codeBrowser.goToField(addr("0x100702a"), "+", 0, 0);
|
codeBrowser.goToField(addr("0x100702a"), "+", 0, 0);
|
||||||
click(codeBrowser, 1);
|
click(codeBrowser, 1);
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
|
|
||||||
codeBrowser.goToField(addr("0x100702b"), "Address", 0, 0);
|
codeBrowser.goToField(addr("0x100702b"), "Address", 0, 0);
|
||||||
assertEquals("0100702b", codeBrowser.getCurrentFieldText());
|
assertEquals("0100702b", codeBrowser.getCurrentFieldText());
|
||||||
|
@ -525,6 +525,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest {
|
||||||
goTo(codeBrowser, addr("0x1007000"));
|
goTo(codeBrowser, addr("0x1007000"));
|
||||||
|
|
||||||
for (int i = 0; i < 300; i++) {
|
for (int i = 0; i < 300; i++) {
|
||||||
|
|
||||||
cursorRight(fp);
|
cursorRight(fp);
|
||||||
|
|
||||||
if (!codeBrowser.getCurrentFieldText().equals(cb2.getCurrentFieldText())) {
|
if (!codeBrowser.getCurrentFieldText().equals(cb2.getCurrentFieldText())) {
|
||||||
|
@ -533,11 +534,11 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest {
|
||||||
assertEquals(codeBrowser.getCurrentFieldText(), cb2.getCurrentFieldText());
|
assertEquals(codeBrowser.getCurrentFieldText(), cb2.getCurrentFieldText());
|
||||||
|
|
||||||
if (!codeBrowser.getCurrentFieldLoction().equals(cb2.getCurrentFieldLoction())) {
|
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());
|
assertEquals(codeBrowser.getCurrentFieldLoction(), cb2.getCurrentFieldLoction());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -561,13 +562,13 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest {
|
||||||
|
|
||||||
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
codeBrowser.goToField(addr("0x1007000"), "+", 0, 0);
|
||||||
click(codeBrowser, 1);
|
click(codeBrowser, 1);
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
codeBrowser.goToField(addr("0x1007023"), "+", 0, 0);
|
codeBrowser.goToField(addr("0x1007023"), "+", 0, 0);
|
||||||
click(codeBrowser, 1);
|
click(codeBrowser, 1);
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
codeBrowser.goToField(addr("0x100702a"), "+", 0, 0);
|
codeBrowser.goToField(addr("0x100702a"), "+", 0, 0);
|
||||||
click(codeBrowser, 1);
|
click(codeBrowser, 1);
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
|
|
||||||
// this should open the other structure
|
// this should open the other structure
|
||||||
codeBrowser.goToField(addr("0x1007023"), "Address", 0, 0);
|
codeBrowser.goToField(addr("0x1007023"), "Address", 0, 0);
|
||||||
|
@ -809,7 +810,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest {
|
||||||
fieldOptions.setInt(name, 0);
|
fieldOptions.setInt(name, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
waitForPostedSwingRunnables();
|
waitForSwing();
|
||||||
plugin.updateNow();
|
plugin.updateNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -187,7 +187,7 @@ public class SearchTextPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assertNotNull(commentsCB);
|
assertNotNull(commentsCB);
|
||||||
assertSelected(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");
|
JCheckBox csCB = (JCheckBox) findButton(dialog.getComponent(), "Case Sensitive");
|
||||||
assertNotNull(csCB);
|
assertNotNull(csCB);
|
||||||
assertNotSelected(csCB);
|
assertNotSelected(csCB);
|
||||||
|
@ -216,7 +216,7 @@ public class SearchTextPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void testSearchOptions() throws Exception {
|
public void testSearchOptions() throws Exception {
|
||||||
// verify that the search dialog allows for searching Functions,
|
// 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.
|
// mnemonics and values.
|
||||||
SearchTextDialog dialog = getDialog();
|
SearchTextDialog dialog = getDialog();
|
||||||
JCheckBox cb = (JCheckBox) findButton(dialog.getComponent(), "Functions");
|
JCheckBox cb = (JCheckBox) findButton(dialog.getComponent(), "Functions");
|
||||||
|
@ -419,6 +419,8 @@ public class SearchTextPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
loc = cbPlugin.getCurrentLocation();
|
loc = cbPlugin.getCurrentLocation();
|
||||||
assertEquals(getAddr(0x01004192), loc.getAddress());
|
assertEquals(getAddr(0x01004192), loc.getAddress());
|
||||||
assertTrue(loc instanceof CommentFieldLocation);
|
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());
|
assertEquals(CodeUnit.PLATE_COMMENT, ((CommentFieldLocation) loc).getCommentType());
|
||||||
|
|
||||||
pressButton(searchButton);
|
pressButton(searchButton);
|
||||||
|
@ -667,7 +669,7 @@ public class SearchTextPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
//
|
//
|
||||||
// test marker stuff
|
// test marker stuff
|
||||||
//
|
//
|
||||||
AddressSet set = getAddressesFromModel(model);
|
AddressSet set = getAddressesFromModel(model);
|
||||||
MarkerService markerService = tool.getService(MarkerService.class);
|
MarkerService markerService = tool.getService(MarkerService.class);
|
||||||
MarkerSet markerSet = markerService.getMarkerSet("Search", program);
|
MarkerSet markerSet = markerService.getMarkerSet("Search", program);
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.util.viewer.field;
|
package ghidra.app.util.viewer.field;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -57,10 +56,6 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
private Program program;
|
private Program program;
|
||||||
private GoToService goToService;
|
private GoToService goToService;
|
||||||
|
|
||||||
public PlateFieldFactoryTest() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
|
|
||||||
|
@ -151,8 +146,9 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
cu.setCommentAsArray(CodeUnit.PLATE_COMMENT,
|
cu.setCommentAsArray(CodeUnit.PLATE_COMMENT,
|
||||||
new String[] { "this is", "a plate comment" });
|
new String[] { "this is", "a plate comment" });
|
||||||
// create a reference to addr
|
// create a reference to addr
|
||||||
program.getReferenceManager().addMemoryReference(getAddr(0x010023ee), addr,
|
program.getReferenceManager()
|
||||||
RefType.DATA, SourceType.USER_DEFINED, 0);
|
.addMemoryReference(getAddr(0x010023ee), addr,
|
||||||
|
RefType.DATA, SourceType.USER_DEFINED, 0);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
program.endTransaction(transactionID, true);
|
program.endTransaction(transactionID, true);
|
||||||
|
@ -189,8 +185,9 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
CodeUnit cu = program.getListing().getCodeUnitAt(addr);
|
CodeUnit cu = program.getListing().getCodeUnitAt(addr);
|
||||||
cu.setCommentAsArray(CodeUnit.PLATE_COMMENT, new String[] { originalText });
|
cu.setCommentAsArray(CodeUnit.PLATE_COMMENT, new String[] { originalText });
|
||||||
// create a reference to addr
|
// create a reference to addr
|
||||||
program.getReferenceManager().addMemoryReference(getAddr(0x010023ee), addr,
|
program.getReferenceManager()
|
||||||
RefType.DATA, SourceType.USER_DEFINED, 0);
|
.addMemoryReference(getAddr(0x010023ee), addr,
|
||||||
|
RefType.DATA, SourceType.USER_DEFINED, 0);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
program.endTransaction(transactionID, true);
|
program.endTransaction(transactionID, true);
|
||||||
|
@ -247,7 +244,7 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
public void testShowTransitionPlates() throws Exception {
|
public void testShowTransitionPlates() throws Exception {
|
||||||
|
|
||||||
// no plate comment
|
// 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);
|
setBooleanOption(PlateFieldFactory.SHOW_TRANSITION_PLATES_OPTION, true);
|
||||||
|
|
||||||
|
@ -282,7 +279,7 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
public void testShowSubroutinePlates() throws Exception {
|
public void testShowSubroutinePlates() throws Exception {
|
||||||
|
|
||||||
// no subroutine plate comment
|
// 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);
|
setBooleanOption(PlateFieldFactory.SHOW_SUBROUTINE_PLATES_OPTION, true);
|
||||||
|
|
||||||
|
@ -297,28 +294,13 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void testLinesBeforeFunction() throws Exception {
|
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);
|
setIntOption(PlateFieldFactory.LINES_BEFORE_FUNCTIONS_OPTION, 2);
|
||||||
|
|
||||||
assertTrue(cb.goToField(getAddr(0x1001300), PlateFieldFactory.FIELD_NAME, 1, 1));
|
assertTrue(cb.goToField(getAddr(0x1001300), PlateFieldFactory.FIELD_NAME, 1, 1));
|
||||||
ListingTextField tf = (ListingTextField) cb.getCurrentField();
|
ListingTextField tf = (ListingTextField) cb.getCurrentField();
|
||||||
assertEquals(2, tf.getNumRows());
|
assertEquals(2, tf.getNumRows());
|
||||||
// list.add(cu.getMinAddress());
|
|
||||||
//
|
|
||||||
// ArrayList<Address> list = new ArrayList<Address>();
|
|
||||||
// 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
|
@Test
|
||||||
|
@ -354,7 +336,7 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
Listing listing = program.getListing();
|
Listing listing = program.getListing();
|
||||||
|
|
||||||
CodeUnit cu = listing.getCodeUnitAt(getAddr(0x1001500));
|
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");
|
createPlateComment(cu, "This is a plate comment");
|
||||||
|
|
||||||
|
@ -465,8 +447,9 @@ public class PlateFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
int transactionID = program.startTransaction("test");
|
int transactionID = program.startTransaction("test");
|
||||||
CodeUnit cu = program.getListing().getCodeUnitAt(addr);
|
CodeUnit cu = program.getListing().getCodeUnitAt(addr);
|
||||||
try {
|
try {
|
||||||
program.getSymbolTable().createLabel(addr, testName.getMethodName(),
|
program.getSymbolTable()
|
||||||
SourceType.USER_DEFINED);
|
.createLabel(addr, testName.getMethodName(),
|
||||||
|
SourceType.USER_DEFINED);
|
||||||
cu.setComment(CodeUnit.PLATE_COMMENT,
|
cu.setComment(CodeUnit.PLATE_COMMENT,
|
||||||
"this is a comment\ngo to the address 0x010028de");
|
"this is a comment\ngo to the address 0x010028de");
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ public class ClippingTextField implements TextField {
|
||||||
protected int startX;
|
protected int startX;
|
||||||
private int width;
|
private int width;
|
||||||
private int preferredWidth;
|
private int preferredWidth;
|
||||||
|
private int numDataRows;
|
||||||
|
|
||||||
private String fullText;
|
private String fullText;
|
||||||
private boolean isClipped;
|
private boolean isClipped;
|
||||||
|
@ -49,28 +50,40 @@ public class ClippingTextField implements TextField {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new ClippingTextField that allows the cursor beyond the end
|
* 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.
|
||||||
*
|
*
|
||||||
* <pre>
|
* @param startX The x position of the field
|
||||||
* this(startX, width, new AttributedString[] { textElement }, hlFactory, true);
|
* @param width The width of the field
|
||||||
* </pre>
|
* @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,
|
public ClippingTextField(int startX, int width, FieldElement textElement,
|
||||||
HighlightFactory hlFactory) {
|
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.
|
||||||
|
*
|
||||||
|
* <p>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.startX = startX;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
|
this.numDataRows = numDataRows;
|
||||||
|
this.textElement = textElement;
|
||||||
|
this.hlFactory = hlFactory;
|
||||||
this.preferredWidth = textElement.getStringWidth();
|
this.preferredWidth = textElement.getStringWidth();
|
||||||
|
|
||||||
clip(width);
|
clip(width);
|
||||||
|
@ -139,7 +152,7 @@ public class ClippingTextField implements TextField {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getNumDataRows() {
|
public int getNumDataRows() {
|
||||||
return 1;
|
return numDataRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -336,18 +349,13 @@ public class ClippingTextField implements TextField {
|
||||||
@Override
|
@Override
|
||||||
public RowColLocation screenToDataLocation(int screenRow, int screenColumn) {
|
public RowColLocation screenToDataLocation(int screenRow, int screenColumn) {
|
||||||
return originalElement.getDataLocationForCharacterIndex(screenColumn);
|
return originalElement.getDataLocationForCharacterIndex(screenColumn);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) {
|
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) {
|
||||||
int column = textElement.getCharacterIndexForDataLocation(dataRow, dataColumn);
|
int column = textElement.getCharacterIndexForDataLocation(dataRow, dataColumn);
|
||||||
if (column < 0) {
|
if (column < 0) {
|
||||||
// place at the end if past the end
|
return new DefaultRowColLocation(0, textElement.length());
|
||||||
if (dataColumn >= textElement.length()) {
|
|
||||||
return new DefaultRowColLocation(0, textElement.length());
|
|
||||||
}
|
|
||||||
return new DefaultRowColLocation();
|
|
||||||
}
|
}
|
||||||
return new RowColLocation(0, column);
|
return new RowColLocation(0, column);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.List;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
|
|
||||||
import docking.widgets.fieldpanel.support.RowColLocation;
|
import docking.widgets.fieldpanel.support.RowColLocation;
|
||||||
|
import generic.json.Json;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A FieldElement that is composed of other FieldElements. The elements are laid out horizontally.
|
* 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;
|
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
|
@Override
|
||||||
public FieldElement replaceAll(char[] targets, char repacement) {
|
public FieldElement replaceAll(char[] targets, char repacement) {
|
||||||
FieldElement[] newStrings = new FieldElement[fieldElements.length];
|
FieldElement[] newStrings = new FieldElement[fieldElements.length];
|
||||||
|
@ -191,16 +152,6 @@ public class CompositeFieldElement implements FieldElement {
|
||||||
return new CompositeFieldElement(newStrings);
|
return new CompositeFieldElement(newStrings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class IndexedOffset {
|
|
||||||
int index;
|
|
||||||
int offset;
|
|
||||||
|
|
||||||
IndexedOffset(int index, int offset) {
|
|
||||||
this.index = index;
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FieldElement getFieldElement(int column) {
|
public FieldElement getFieldElement(int column) {
|
||||||
IndexedOffset startPos = getIndexedOffsetForCharPosition(column);
|
IndexedOffset startPos = getIndexedOffsetForCharPosition(column);
|
||||||
|
@ -217,6 +168,46 @@ public class CompositeFieldElement implements FieldElement {
|
||||||
return getText();
|
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
|
// Location Info
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
@ -229,6 +220,7 @@ public class CompositeFieldElement implements FieldElement {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCharacterIndexForDataLocation(int dataRow, int dataColumn) {
|
public int getCharacterIndexForDataLocation(int dataRow, int dataColumn) {
|
||||||
|
|
||||||
int columnsSoFar = 0;
|
int columnsSoFar = 0;
|
||||||
for (int i = fieldElements.length - 1; i >= 0; i--) {
|
for (int i = fieldElements.length - 1; i >= 0; i--) {
|
||||||
columnsSoFar += fieldElements[i].length();
|
columnsSoFar += fieldElements[i].length();
|
||||||
|
@ -242,4 +234,20 @@ public class CompositeFieldElement implements FieldElement {
|
||||||
|
|
||||||
return -1;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -342,12 +342,15 @@ public class CompositeVerticalLayoutTextField implements TextField {
|
||||||
}
|
}
|
||||||
|
|
||||||
private FieldRow getFieldRowFromDataRow(int dataRow) {
|
private FieldRow getFieldRowFromDataRow(int dataRow) {
|
||||||
|
|
||||||
int currentRow = 0;
|
int currentRow = 0;
|
||||||
for (FieldRow row : fieldRows) {
|
for (FieldRow row : fieldRows) {
|
||||||
if (currentRow >= dataRow) {
|
int length = row.field.getNumDataRows();
|
||||||
|
|
||||||
|
if (currentRow + length > dataRow) {
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
currentRow += row.field.getNumDataRows();
|
currentRow += length;
|
||||||
}
|
}
|
||||||
return fieldRows.get(fieldRows.size() - 1);
|
return fieldRows.get(fieldRows.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,9 @@ import java.util.*;
|
||||||
import docking.widgets.fieldpanel.support.HighlightFactory;
|
import docking.widgets.fieldpanel.support.HighlightFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides a TextField implementation that takes multiple AttributedStrings and places
|
* This class provides a TextField implementation that takes multiple AttributedString field
|
||||||
* as many that will fit on a line without clipping before continuing to the next line.
|
* elements and places as many that will fit on a line without clipping before continuing to the
|
||||||
|
* next line.
|
||||||
*/
|
*/
|
||||||
public class FlowLayoutTextField extends VerticalLayoutTextField {
|
public class FlowLayoutTextField extends VerticalLayoutTextField {
|
||||||
|
|
||||||
|
@ -67,27 +68,35 @@ public class FlowLayoutTextField extends VerticalLayoutTextField {
|
||||||
int currentIndex = 0;
|
int currentIndex = 0;
|
||||||
while (currentIndex < elements.size()) {
|
while (currentIndex < elements.size()) {
|
||||||
int numberPerLine = getNumberOfElementsPerLine(elements, currentIndex, width);
|
int numberPerLine = getNumberOfElementsPerLine(elements, currentIndex, width);
|
||||||
subFields.add(createLine(elements, currentIndex, numberPerLine));
|
subFields.add(createLineFromElements(elements, currentIndex, numberPerLine));
|
||||||
currentIndex += numberPerLine;
|
currentIndex += numberPerLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
return subFields;
|
return subFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CompositeFieldElement createLine(List<FieldElement> elements, int from,
|
@Override
|
||||||
int length) {
|
protected TextField createFieldForLine(FieldElement element) {
|
||||||
return new CompositeFieldElement(elements.subList(from, from + length));
|
CompositeFieldElement composite = (CompositeFieldElement) element;
|
||||||
|
int numDataRows = composite.getNumElements();
|
||||||
|
return new ClippingTextField(startX, width, element, numDataRows, hlFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompositeFieldElement createLineFromElements(List<FieldElement> elements,
|
||||||
|
int start, int length) {
|
||||||
|
return new CompositeFieldElement(elements.subList(start, start + length));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getNumberOfElementsPerLine(List<FieldElement> elements, int start,
|
private static int getNumberOfElementsPerLine(List<FieldElement> elements, int start,
|
||||||
int width) {
|
int width) {
|
||||||
|
|
||||||
int currentWidth = 0;
|
int currentWidth = 0;
|
||||||
int count = 0;
|
for (int i = start; i < elements.size(); i++) {
|
||||||
for (FieldElement element : elements) {
|
FieldElement element = elements.get(i);
|
||||||
currentWidth += element.getStringWidth();
|
currentWidth += element.getStringWidth();
|
||||||
count++;
|
|
||||||
if (currentWidth > width) {
|
if (currentWidth > width) {
|
||||||
return Math.max(count - 1, 1);
|
int count = i - start;
|
||||||
|
return Math.max(count, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return elements.size() - start;
|
return elements.size() - start;
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
package docking.widgets.fieldpanel.field;
|
package docking.widgets.fieldpanel.field;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.JComponent;
|
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.FieldBackgroundColorManager;
|
||||||
import docking.widgets.fieldpanel.internal.PaintContext;
|
import docking.widgets.fieldpanel.internal.PaintContext;
|
||||||
import docking.widgets.fieldpanel.support.*;
|
import docking.widgets.fieldpanel.support.*;
|
||||||
|
import generic.json.Json;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides a TextField implementation that takes multiple FieldElements and places
|
* 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 {
|
public class VerticalLayoutTextField implements TextField {
|
||||||
|
|
||||||
protected List<TextField> 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<FieldRow> subFields;
|
||||||
protected int startX;
|
protected int startX;
|
||||||
protected int width;
|
protected int width;
|
||||||
protected int preferredWidth;
|
protected int preferredWidth;
|
||||||
|
@ -136,16 +141,16 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void calculateHeight() {
|
protected void calculateHeight() {
|
||||||
heightAbove = (subFields.get(0)).getHeightAbove();
|
heightAbove = (subFields.get(0)).field.getHeightAbove();
|
||||||
for (Field field : subFields) {
|
for (FieldRow fieldRow : subFields) {
|
||||||
height += field.getHeight();
|
height += fieldRow.field.getHeight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int calculatePreferredWidth() {
|
private int calculatePreferredWidth() {
|
||||||
int widest = 0;
|
int widest = 0;
|
||||||
for (Field field : subFields) {
|
for (FieldRow fieldRow : subFields) {
|
||||||
widest = Math.max(widest, field.getPreferredWidth());
|
widest = Math.max(widest, fieldRow.field.getPreferredWidth());
|
||||||
}
|
}
|
||||||
return widest;
|
return widest;
|
||||||
}
|
}
|
||||||
|
@ -197,20 +202,21 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getNumCols(int row) {
|
public int getNumCols(int row) {
|
||||||
Field f = subFields.get(row);
|
Field f = getField(row);
|
||||||
return f.getNumCols(0);
|
return f.getNumCols(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRow(int y) {
|
public int getRow(int y) {
|
||||||
if (y < 0) {
|
if (y < -heightAbove) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int heightSoFar = 0;
|
int heightSoFar = -heightAbove;
|
||||||
|
|
||||||
int n = subFields.size();
|
int n = subFields.size();
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
Field f = subFields.get(i);
|
Field f = getField(i);
|
||||||
heightSoFar += f.getHeight();
|
heightSoFar += f.getHeight();
|
||||||
if (heightSoFar > y) {
|
if (heightSoFar > y) {
|
||||||
return i;
|
return i;
|
||||||
|
@ -221,7 +227,7 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCol(int row, int x) {
|
public int getCol(int row, int x) {
|
||||||
Field f = subFields.get(row);
|
Field f = getField(row);
|
||||||
return f.getCol(0, x);
|
return f.getCol(0, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +236,7 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
|
|
||||||
int y = -heightAbove;
|
int y = -heightAbove;
|
||||||
for (int i = 0; i < row; i++) {
|
for (int i = 0; i < row; i++) {
|
||||||
Field f = subFields.get(row);
|
Field f = getField(i);
|
||||||
y += f.getHeight();
|
y += f.getHeight();
|
||||||
}
|
}
|
||||||
return y;
|
return y;
|
||||||
|
@ -238,7 +244,7 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getX(int row, int col) {
|
public int getX(int row, int col) {
|
||||||
Field f = subFields.get(row);
|
Field f = getField(row);
|
||||||
return f.getX(0, col);
|
return f.getX(0, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +254,7 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
if ((row < 0) || (row >= subFields.size())) {
|
if ((row < 0) || (row >= subFields.size())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Field f = subFields.get(row);
|
Field f = getField(row);
|
||||||
return f.isValid(0, col);
|
return f.isValid(0, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +301,7 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
int translatedY = 0;
|
int translatedY = 0;
|
||||||
int extraSpace = rowSeparator.length();
|
int extraSpace = rowSeparator.length();
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
ClippingTextField subField = (ClippingTextField) subFields.get(i);
|
ClippingTextField subField = (ClippingTextField) getField(i);
|
||||||
int subFieldHeight = subField.getHeight();
|
int subFieldHeight = subField.getHeight();
|
||||||
int endY = startY + subFieldHeight;
|
int endY = startY + subFieldHeight;
|
||||||
|
|
||||||
|
@ -332,7 +338,7 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
private void print(Graphics g, PaintContext context) {
|
private void print(Graphics g, PaintContext context) {
|
||||||
int n = subFields.size();
|
int n = subFields.size();
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
ClippingTextField clippingField = (ClippingTextField) subFields.get(i);
|
ClippingTextField clippingField = (ClippingTextField) getField(i);
|
||||||
|
|
||||||
clippingField.print(g, context);
|
clippingField.print(g, context);
|
||||||
|
|
||||||
|
@ -346,10 +352,10 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
if ((row < 0) || (row >= subFields.size())) {
|
if ((row < 0) || (row >= subFields.size())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Field f = subFields.get(row);
|
Field f = getField(row);
|
||||||
Rectangle r = f.getCursorBounds(0, col);
|
Rectangle r = f.getCursorBounds(0, col);
|
||||||
for (int i = 0; i < row; i++) {
|
for (int i = 0; i < row; i++) {
|
||||||
f = subFields.get(row);
|
f = getField(i);
|
||||||
r.y += f.getHeight();
|
r.y += f.getHeight();
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
|
@ -370,10 +376,11 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
if ((topOfScreen < -heightAbove) || (topOfScreen > height - heightAbove)) {
|
if ((topOfScreen < -heightAbove) || (topOfScreen > height - heightAbove)) {
|
||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
|
|
||||||
int row = getRow(topOfScreen);
|
int row = getRow(topOfScreen);
|
||||||
int y = getY(row);
|
int y = getY(row);
|
||||||
int rowOffset = topOfScreen - y;
|
int rowOffset = topOfScreen - y;
|
||||||
int rowHeight = (subFields.get(row)).getHeight();
|
int rowHeight = getField(row).getHeight();
|
||||||
if (direction > 0) { // if scrolling down
|
if (direction > 0) { // if scrolling down
|
||||||
return rowHeight - rowOffset;
|
return rowHeight - rowOffset;
|
||||||
}
|
}
|
||||||
|
@ -399,14 +406,6 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
isPrimary = state;
|
isPrimary = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of subfields in this field.
|
|
||||||
* @return the list of subfields in this field.
|
|
||||||
*/
|
|
||||||
public List<Field> getSubfields() {
|
|
||||||
return Collections.unmodifiableList(subFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getHeightAbove() {
|
public int getHeightAbove() {
|
||||||
return heightAbove;
|
return heightAbove;
|
||||||
|
@ -425,17 +424,17 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
@Override
|
@Override
|
||||||
public FieldElement getFieldElement(int screenRow, int screenColumn) {
|
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
|
int fieldRow = 0; // each field is on a single row
|
||||||
return f.getFieldElement(fieldRow, screenColumn);
|
return f.getFieldElement(fieldRow, screenColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<TextField> layoutElements(List<FieldElement> textElements, int maxLines) {
|
protected List<FieldRow> layoutElements(List<FieldElement> textElements, int maxLines) {
|
||||||
List<TextField> newSubFields = new ArrayList<>();
|
List<FieldRow> newSubFields = new ArrayList<>();
|
||||||
|
|
||||||
boolean tooManyLines = textElements.size() > maxLines;
|
boolean tooManyLines = textElements.size() > maxLines;
|
||||||
|
int currentRow = 0;
|
||||||
for (int i = 0; i < textElements.size() && i < maxLines; i++) {
|
for (int i = 0; i < textElements.size() && i < maxLines; i++) {
|
||||||
FieldElement element = textElements.get(i);
|
FieldElement element = textElements.get(i);
|
||||||
if (tooManyLines && (i == maxLines - 1)) {
|
if (tooManyLines && (i == maxLines - 1)) {
|
||||||
|
@ -444,9 +443,13 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
elements[1] = new StrutFieldElement(500);
|
elements[1] = new StrutFieldElement(500);
|
||||||
element = new CompositeFieldElement(elements);
|
element = new CompositeFieldElement(elements);
|
||||||
}
|
}
|
||||||
TextField field = new ClippingTextField(startX, width, element, hlFactory);
|
TextField field = createFieldForLine(element);
|
||||||
newSubFields.add(field);
|
int modelRow = currentRow;
|
||||||
|
int screenRow = newSubFields.size();
|
||||||
|
newSubFields.add(new FieldRow(field, modelRow, screenRow));
|
||||||
isClipped |= field.isClipped();
|
isClipped |= field.isClipped();
|
||||||
|
|
||||||
|
currentRow += field.getNumRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
isClipped |= tooManyLines;
|
isClipped |= tooManyLines;
|
||||||
|
@ -455,38 +458,36 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates the row and column to a String index and character offset into
|
* Create the text field for given field element
|
||||||
* that string.
|
* @param element the element
|
||||||
* @param screenRow the row containing the location.
|
* @return the field
|
||||||
* @param screenColumn the character position in the row of the location
|
|
||||||
* @return a MultiStringLocation containing the string index and position
|
|
||||||
* within that string.
|
|
||||||
*/
|
*/
|
||||||
|
protected TextField createFieldForLine(FieldElement element) {
|
||||||
|
return new ClippingTextField(startX, width, element, hlFactory);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RowColLocation screenToDataLocation(int screenRow, int screenColumn) {
|
public RowColLocation screenToDataLocation(int screenRow, int screenColumn) {
|
||||||
|
|
||||||
screenRow = Math.min(screenRow, subFields.size() - 1);
|
screenRow = Math.min(screenRow, subFields.size() - 1);
|
||||||
screenRow = Math.max(screenRow, 0);
|
screenRow = Math.max(screenRow, 0);
|
||||||
|
|
||||||
TextField field = subFields.get(screenRow);
|
TextField field = getField(screenRow);
|
||||||
screenColumn = Math.min(screenColumn, field.getText().length());
|
screenColumn = Math.min(screenColumn, field.getText().length());
|
||||||
screenColumn = Math.max(screenColumn, 0);
|
screenColumn = Math.max(screenColumn, 0);
|
||||||
|
|
||||||
int fieldRow = 0; // each field is on a single row
|
int dataRow = getDataRow(field);
|
||||||
return field.screenToDataLocation(fieldRow, screenColumn);
|
return field.screenToDataLocation(dataRow, screenColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) {
|
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) {
|
||||||
|
|
||||||
if (dataRow >= getNumRows()) {
|
FieldRow fieldRow = getFieldRowFromDataRow(dataRow);
|
||||||
TextField lastField = subFields.get(subFields.size());
|
TextField field = fieldRow.field;
|
||||||
return new DefaultRowColLocation(lastField.getText().length(), subFields.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField field = subFields.get(dataRow);
|
|
||||||
RowColLocation location = field.dataToScreenLocation(dataRow, dataColumn);
|
RowColLocation location = field.dataToScreenLocation(dataRow, dataColumn);
|
||||||
return location.withRow(dataRow);
|
int screenRow = fieldRow.screenRow;
|
||||||
|
return location.withRow(screenRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -517,7 +518,8 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
}
|
}
|
||||||
|
|
||||||
int lastRow = n - 1;
|
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);
|
return new DefaultRowColLocation(lastRow, lastColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,4 +527,54 @@ public class VerticalLayoutTextField implements TextField {
|
||||||
public boolean isClipped() {
|
public boolean isClipped() {
|
||||||
return isClipped;
|
return isClipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the view's text lines of this field
|
||||||
|
* @return the lines
|
||||||
|
*/
|
||||||
|
protected List<String> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,22 @@ public class FlowLayoutTextFieldTest extends AbstractGenericTest {
|
||||||
FontMetrics fm = tk.getFontMetrics(font);
|
FontMetrics fm = tk.getFontMetrics(font);
|
||||||
List<FieldElement> elements = new ArrayList<>();
|
List<FieldElement> 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("Hello ", Color.BLUE, fm), 0, 0));
|
||||||
elements.add(new TextFieldElement(
|
elements.add(new TextFieldElement(
|
||||||
new AttributedString("World ", Color.RED, fm, true, Color.BLUE), 1, 0));
|
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, 0), textField.dataToScreenLocation(2, 0));
|
||||||
assertEquals(new RowColLocation(1, 4), textField.dataToScreenLocation(2, 4));
|
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, 0), textField.dataToScreenLocation(3, 0));
|
||||||
assertEquals(new RowColLocation(2, 4), textField.dataToScreenLocation(3, 4));
|
assertEquals(new RowColLocation(2, 4), textField.dataToScreenLocation(3, 4));
|
||||||
|
|
||||||
assertEquals(new RowColLocation(0, 0), textField.dataToScreenLocation(0, 12));
|
assertEquals(new DefaultRowColLocation(0, 12), textField.dataToScreenLocation(0, 12));
|
||||||
assertEquals(new RowColLocation(0, 0), textField.dataToScreenLocation(0, 75));
|
assertEquals(new DefaultRowColLocation(0, 12), textField.dataToScreenLocation(0, 75));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetRowColumn() {
|
public void testTextOffsetToScreenLocation() {
|
||||||
assertEquals(new RowColLocation(0, 0), textField.textOffsetToScreenLocation(0));
|
assertEquals(new RowColLocation(0, 0), textField.textOffsetToScreenLocation(0));
|
||||||
assertEquals(new RowColLocation(0, 5), textField.textOffsetToScreenLocation(5));
|
assertEquals(new RowColLocation(0, 5), textField.textOffsetToScreenLocation(5));
|
||||||
assertEquals(new RowColLocation(0, 6), textField.textOffsetToScreenLocation(6));
|
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, 3), textField.textOffsetToScreenLocation(15));
|
||||||
|
|
||||||
assertEquals(new RowColLocation(1, 18), textField.textOffsetToScreenLocation(30));
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -324,7 +324,7 @@ public class CompositeVerticalLayoutTextFieldTest extends AbstractGenericTest {
|
||||||
assertRowCol(1, 5, field.dataToScreenLocation(1, 5));
|
assertRowCol(1, 5, field.dataToScreenLocation(1, 5));
|
||||||
|
|
||||||
// try accessing clipped rows
|
// 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(2, 5));
|
||||||
assertRowCol(1, 5, field.dataToScreenLocation(20, 50));
|
assertRowCol(1, 5, field.dataToScreenLocation(20, 50));
|
||||||
}
|
}
|
||||||
|
|
|
@ -435,7 +435,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
String operandPrefix = "dword ptr [EBP + ";
|
String operandPrefix = "dword ptr [EBP + ";
|
||||||
String operandReferenceName = "destStr]";
|
String operandReferenceName = "destStr]";
|
||||||
OperandFieldLocation variableOperandReferenceLocation = new OperandFieldLocation(program,
|
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);
|
codeBrowserPlugin.goTo(variableOperandReferenceLocation);
|
||||||
|
|
||||||
DockingAction pasteAction = getAction(codeBrowserClipboardProvider, PASTE_ACTION_NAME);
|
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,
|
* 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
|
* the selection mechanism does not work as expected. Focus changes can happen
|
||||||
* indeterminately during parallel batch testing.
|
* indeterminately during parallel batch testing.
|
||||||
*/
|
*/
|
||||||
private void removeFieldPanelFocusListeners(Container c) {
|
private void removeFieldPanelFocusListeners(Container c) {
|
||||||
|
|
Loading…
Reference in a new issue