Merge remote-tracking branch

'origin/GP-1093-dragonmacher-listing-xref-condensing' (Closes #1305)
This commit is contained in:
Ryan Kurtz 2021-09-15 07:06:59 -04:00
commit ec6bc9c22d
46 changed files with 3421 additions and 937 deletions

View file

@ -927,25 +927,46 @@
<P><B>Display Local Block -</B> Prepends the name of the memory block containing the XREF
source address to each XREF.</P>
<P><B>Maximum Number of XREFs To Display -</B> The maximum number of lines used to display
XREFs. Additional XREFs will not be displayed.</P>
<P><B>Namespace Options: </B>
<BLOCKQUOTE>
<P><B>Display Non-local Namespace -</B> Select this option to prepend the namespace to all
XREFs that are not from an instruction within the current Function's body.&nbsp; Currently,
this would only affect XREFs that originate in some other function.<BR>
</P>
<P><B>Display Library in Namespace -</B> Include the library name in the namespace.<BR>
</P>
<P><B>Display Local Namespace -</B> Select this option to prepend the namespace to all
XREFs that are from the current Function.<BR>
</P>
<P><SPAN style="font-weight: bold;">Use Local Namespace Override</SPAN> - Select this
<BLOCKQUOTE>
<P><B>Use Local Namespace Override</B> - Select this
option to show a fixed prefix for local XREFs instead of the function's name.&nbsp; This
option is only available if the "Display Local Namespace" option is on.&nbsp; The text box
contains the prefix to use for local XREFs.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P><B>Display Reference Type -</B> Shows a single letter to represent the type of reference.
Some of the possible types are:
<CODE>Read (R), Write (W), Data (*), Call (c), Jump (j) and Thunk (T)</CODE>.
<P><B>Group by Function -</B> Groups all references by the containing source function.
With this option off, all references within a function are displayed on their on row.
With this feature on, each function will get a single row, with all references displayed on
that row.
<P><B>Maximum Number of XREFs To Display -</B> The maximum number of lines used to display
XREFs. Additional XREFs will not be displayed.</P>
<P><B>Sort References by -</B> Allows the references to be sorted by Address or by type.
This is most useful when <B>Group by Function</B> is off.
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2><A name="Listing_Popups"></A>Popups</H2>
<BLOCKQUOTE>

View file

@ -988,8 +988,8 @@ public class CodeBrowserPlugin extends Plugin
return; // not sure if this can happen
}
Set<Reference> refs = XReferenceUtil.getAllXrefs(location);
XReferenceUtil.showAllXrefs(connectedProvider, tool, service, location, refs);
Set<Reference> refs = XReferenceUtils.getAllXrefs(location);
XReferenceUtils.showXrefs(connectedProvider, tool, service, location, refs);
}
private GhidraProgramTableModel<Address> createTableModel(CodeUnitIterator iterator,
@ -1065,16 +1065,13 @@ public class CodeBrowserPlugin extends Plugin
*/
public boolean goToField(Address a, String fieldName, int occurrence, int row, int col,
boolean scroll) {
boolean result = SystemUtilities
.runSwingNow(() -> doGoToField(a, fieldName, occurrence, row, col, scroll));
return result;
return Swing.runNow(() -> doGoToField(a, fieldName, occurrence, row, col, scroll));
}
private boolean doGoToField(Address a, String fieldName, int occurrence, int row, int col,
boolean scroll) {
SystemUtilities.assertThisIsTheSwingThread("GoTo must be performed on the Swing thread");
Swing.assertSwingThread("'Go To' must be performed on the Swing thread");
// make sure that the code browser is ready to go--sometimes it is not, due to timing
// during the testing process, like when the tool is first loaded.

View file

@ -32,10 +32,12 @@ import ghidra.util.table.ReferencesFromTableModel;
import ghidra.util.table.field.ReferenceEndpoint;
/**
* A utility class to handle the generation of
* direct and offcut cross-reference (xref) lists
* A utility class to handle the generation of direct and offcut cross-reference (xref) lists
* on code units and stack variables.
*
* @deprecated deprecated for 10.1; removal for 10.3 or later
*/
@Deprecated // Use XReferenceUtils instead
public class XReferenceUtil {
private final static Address[] EMPTY_ADDR_ARRAY = new Address[0];
private final static Reference[] EMPTY_REF_ARRAY = new Reference[0];
@ -71,9 +73,8 @@ public class XReferenceUtil {
if (prog == null) {
return EMPTY_ADDR_ARRAY;
}
List<Address> xrefList = new ArrayList<Address>();
//lookup the direct xrefs to the current code unit
//
List<Address> xrefList = new ArrayList<>();
// lookup the direct xrefs to the current code unit
ReferenceIterator iter = prog.getReferenceManager().getReferencesTo(cu.getMinAddress());
while (iter.hasNext()) {
Reference ref = iter.next();
@ -103,7 +104,7 @@ public class XReferenceUtil {
if (prog == null) {
return EMPTY_REF_ARRAY;
}
List<Reference> xrefList = new ArrayList<Reference>();
List<Reference> xrefList = new ArrayList<>();
//lookup the direct xrefs to the current code unit
//
ReferenceIterator iter = prog.getReferenceManager().getReferencesTo(cu.getMinAddress());
@ -156,7 +157,7 @@ public class XReferenceUtil {
if (prog == null) {
return EMPTY_ADDR_ARRAY;
}
List<Address> offcutList = new ArrayList<Address>();
List<Address> offcutList = new ArrayList<>();
// Lookup the offcut xrefs...
//
if (cu.getLength() > 1) {
@ -195,7 +196,7 @@ public class XReferenceUtil {
if (prog == null) {
return EMPTY_REF_ARRAY;
}
List<Reference> offcutList = new ArrayList<Reference>();
List<Reference> offcutList = new ArrayList<>();
// Lookup the offcut xrefs...
//
if (cu.getLength() > 1) {
@ -227,6 +228,7 @@ public class XReferenceUtil {
* @return count of all offcut xrefs to the code unit
*/
public static int getOffcutXRefCount(CodeUnit cu) {
Program prog = cu.getProgram();
if (prog == null) {
return 0;

View file

@ -0,0 +1,212 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util;
import java.util.*;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.util.query.TableService;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataUtilities;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.util.table.ReferencesFromTableModel;
import ghidra.util.table.field.ReferenceEndpoint;
public class XReferenceUtils {
// Methods in this class treat -1 as a key to return all references and
// not cap the result set.
private final static int ALL_REFS = -1;
/**
* Returns an array containing the first <b><code>max</code></b>
* direct xref references to the specified code unit.
*
* @param cu the code unit to generate the xrefs
* @param max max number of xrefs to get, or -1 to get all references
*
* @return array first <b><code>max</code></b> xrefs to the code unit
*/
public final static List<Reference> getXReferences(CodeUnit cu, int max) {
Program program = cu.getProgram();
if (program == null) {
Collections.emptyList();
}
// lookup the direct xrefs to the current code unit
List<Reference> xrefs = new ArrayList<>();
Address minAddress = cu.getMinAddress();
ReferenceIterator it = program.getReferenceManager().getReferencesTo(minAddress);
while (it.hasNext()) {
if (xrefs.size() - max == 0) {
break;
}
Reference ref = it.next();
xrefs.add(ref);
}
// Check for thunk reference
Function func = program.getFunctionManager().getFunctionAt(minAddress);
if (func != null) {
Address[] thunkAddrs = func.getFunctionThunkAddresses();
if (thunkAddrs != null) {
for (Address thunkAddr : thunkAddrs) {
xrefs.add(new ThunkReference(thunkAddr, func.getEntryPoint()));
}
}
}
return xrefs;
}
/**
* Returns an array containing all offcut xref references to the specified code unit
*
* @param cu the code unit to generate the offcut xrefs
* @param max max number of offcut xrefs to get, or -1 to get all offcut references
* @return array of all offcut xrefs to the code unit
*/
public static List<Reference> getOffcutXReferences(CodeUnit cu, int max) {
Program program = cu.getProgram();
if (program == null) {
return Collections.emptyList();
}
if (cu.getLength() <= 1) {
return Collections.emptyList();
}
List<Reference> offcuts = new ArrayList<>();
ReferenceManager refMgr = program.getReferenceManager();
AddressSet set = new AddressSet(cu.getMinAddress().add(1), cu.getMaxAddress());
AddressIterator it = refMgr.getReferenceDestinationIterator(set, true);
while (it.hasNext()) {
Address addr = it.next();
ReferenceIterator refIter = refMgr.getReferencesTo(addr);
while (refIter.hasNext()) {
if (offcuts.size() - max == 0) {
break;
}
Reference ref = refIter.next();
offcuts.add(ref);
}
}
return offcuts;
}
/**
* Populates the provided lists with the direct and offcut xrefs to the specified variable
*
* @param var variable to get references
* @param xrefs list to put direct references in
* @param offcuts list to put offcut references in
*/
public static void getVariableRefs(Variable var, List<Reference> xrefs,
List<Reference> offcuts) {
getVariableRefs(var, xrefs, offcuts, ALL_REFS);
}
/**
* Populates the provided lists with the direct and offcut xrefs to the specified variable
*
* @param var variable to get references
* @param xrefs list to put direct references in
* @param offcuts list to put offcut references in
* @param max max number of xrefs to get, or -1 to get all references
*/
public static void getVariableRefs(Variable var, List<Reference> xrefs,
List<Reference> offcuts, int max) {
Address addr = var.getMinAddress();
if (addr == null) {
return;
}
Program program = var.getFunction().getProgram();
ReferenceManager refMgr = program.getReferenceManager();
Reference[] refs = refMgr.getReferencesTo(var);
int total = 0;
for (Reference vref : refs) {
if (total++ - max == 0) {
break;
}
if (addr.equals(vref.getToAddress())) {
xrefs.add(vref);
}
else {
offcuts.add(vref);
}
}
}
/**
* Returns all xrefs to the given location. If in data, then xrefs to the specific data
* component will be returned. Otherwise, the code unit containing the address of the
* given location will be used as the source of the xrefs.
*
* @param location the location for which to get xrefs
* @return the xrefs
*/
public static Set<Reference> getAllXrefs(ProgramLocation location) {
CodeUnit cu = DataUtilities.getDataAtLocation(location);
if (cu == null) {
Address toAddress = location.getAddress();
Listing listing = location.getProgram().getListing();
cu = listing.getCodeUnitContaining(toAddress);
}
if (cu == null) {
return Collections.emptySet();
}
List<Reference> xrefs = getXReferences(cu, ALL_REFS);
List<Reference> offcuts = getOffcutXReferences(cu, ALL_REFS);
// Remove duplicates
Set<Reference> set = new HashSet<>();
set.addAll(xrefs);
set.addAll(offcuts);
return set;
}
/**
* Shows all xrefs to the given location in a new table.
*
* @param navigatable the navigatable used for navigation from the table
* @param serviceProvider the service provider needed to wire navigation
* @param service the service needed to show the table
* @param location the location for which to find references
* @param xrefs the xrefs to show
*/
public static void showXrefs(Navigatable navigatable, ServiceProvider serviceProvider,
TableService service, ProgramLocation location, Collection<Reference> xrefs) {
ReferencesFromTableModel model =
new ReferencesFromTableModel(new ArrayList<>(xrefs), serviceProvider,
location.getProgram());
TableComponentProvider<ReferenceEndpoint> provider = service.showTable(
"XRefs to " + location.getAddress().toString(), "XRefs", model, "XRefs", navigatable);
provider.installRemoveItemsAction();
}
}

View file

@ -17,12 +17,11 @@ package ghidra.app.util.exporter;
import java.util.*;
import ghidra.app.util.XReferenceUtil;
import ghidra.program.model.address.Address;
import ghidra.app.util.XReferenceUtils;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceManager;
import ghidra.program.model.symbol.*;
class ReferenceLineDispenser extends AbstractLineDispenser {
@ -35,12 +34,13 @@ class ReferenceLineDispenser extends AbstractLineDispenser {
private Memory memory;
private ReferenceManager referenceManager;
private List<String> lines = new ArrayList<String>();
private List<String> lines = new ArrayList<>();
ReferenceLineDispenser() {
}
ReferenceLineDispenser(boolean forwardRefs, CodeUnit cu, Program program, ProgramTextOptions options) {
ReferenceLineDispenser(boolean forwardRefs, CodeUnit cu, Program program,
ProgramTextOptions options) {
this.memory = program.getMemory();
this.referenceManager = program.getReferenceManager();
this.displayRefHeader = options.isShowReferenceHeaders();
@ -48,13 +48,12 @@ class ReferenceLineDispenser extends AbstractLineDispenser {
this.header = (forwardRefs ? " FWD" : "XREF");
this.headerWidth = options.getRefHeaderWidth();
this.width = options.getRefWidth();
this.fillAmount = options.getAddrWidth()
+ options.getBytesWidth()
+ options.getLabelWidth();
this.fillAmount =
options.getAddrWidth() + options.getBytesWidth() + options.getLabelWidth();
this.isHTML = options.isHTML();
Address [] refs = (forwardRefs ? getForwardRefs(cu) : XReferenceUtil.getXRefList(cu));
Address [] offcuts = (forwardRefs ? EMPTY_ADDR_ARR : XReferenceUtil.getOffcutXRefList(cu));
Address[] refs = (forwardRefs ? getForwardRefs(cu) : getXRefList(cu));
Address[] offcuts = (forwardRefs ? EMPTY_ADDR_ARR : getOffcutXRefList(cu));
processRefs(cu.getMinAddress(), refs, offcuts);
}
@ -67,16 +66,14 @@ class ReferenceLineDispenser extends AbstractLineDispenser {
this.headerWidth = options.getRefHeaderWidth();
this.prefix = options.getCommentPrefix();
this.width = options.getStackVarXrefWidth();
this.fillAmount = options.getStackVarPreNameWidth()
+ options.getStackVarNameWidth()
+ options.getStackVarDataTypeWidth()
+ options.getStackVarOffsetWidth()
+ options.getStackVarCommentWidth();
this.fillAmount = options.getStackVarPreNameWidth() + options.getStackVarNameWidth() +
options.getStackVarDataTypeWidth() + options.getStackVarOffsetWidth() +
options.getStackVarCommentWidth();
this.isHTML = options.isHTML();
List<Reference> xrefs = new ArrayList<Reference>();
List<Reference> offcuts = new ArrayList<Reference>();
XReferenceUtil.getVariableRefs(var, xrefs, offcuts);
List<Reference> xrefs = new ArrayList<>();
List<Reference> offcuts = new ArrayList<>();
XReferenceUtils.getVariableRefs(var, xrefs, offcuts);
Address[] xrefAddr = extractFromAddr(xrefs);
Address[] offcutsAddr = extractFromAddr(offcuts);
@ -84,9 +81,9 @@ class ReferenceLineDispenser extends AbstractLineDispenser {
xrefAddr, offcutsAddr);
}
private Address [] extractFromAddr(List<Reference> refs) {
Address [] addrs = new Address[refs.size()];
for (int i=0; i < addrs.length; i++) {
private Address[] extractFromAddr(List<Reference> refs) {
Address[] addrs = new Address[refs.size()];
for (int i = 0; i < addrs.length; i++) {
addrs[i] = refs.get(i).getFromAddress();
}
Arrays.sort(addrs);
@ -113,18 +110,18 @@ class ReferenceLineDispenser extends AbstractLineDispenser {
////////////////////////////////////////////////////////////////////
private Address [] getForwardRefs(CodeUnit cu) {
private Address[] getForwardRefs(CodeUnit cu) {
boolean showRefs = false;
Address cuAddr = cu.getMinAddress();
Reference [] monRefs = cu.getMnemonicReferences();
Reference[] monRefs = cu.getMnemonicReferences();
Reference primMonRef = referenceManager.getPrimaryReferenceFrom(cuAddr, CodeUnit.MNEMONIC);
showRefs = (monRefs.length == 1 && primMonRef == null) || (monRefs.length > 1);
if (!showRefs) {
int opCount = cu.getNumOperands();
for (int i = 0 ; i < opCount ; ++i) {
Reference [] opRefs = cu.getOperandReferences(i);
for (int i = 0; i < opCount; ++i) {
Reference[] opRefs = cu.getOperandReferences(i);
if (opRefs.length > 1) {
showRefs = true;
break;
@ -136,9 +133,9 @@ class ReferenceLineDispenser extends AbstractLineDispenser {
return EMPTY_ADDR_ARR;
}
Reference [] mRefs = cu.getReferencesFrom();
Address [] refs = new Address[mRefs.length];
for (int i = 0 ; i < mRefs.length ; ++i) {
Reference[] mRefs = cu.getReferencesFrom();
Address[] refs = new Address[mRefs.length];
for (int i = 0; i < mRefs.length; ++i) {
refs[i] = mRefs[i].getToAddress();
}
Arrays.sort(refs);
@ -147,7 +144,7 @@ class ReferenceLineDispenser extends AbstractLineDispenser {
////////////////////////////////////////////////////////////////////
private void processRefs(Address addr, Address [] refs, Address [] offcuts) {
private void processRefs(Address addr, Address[] refs, Address[] offcuts) {
if (width < 1) {
return;
}
@ -157,8 +154,8 @@ class ReferenceLineDispenser extends AbstractLineDispenser {
StringBuffer buf = new StringBuffer();
Address [] all = new Address[refs.length + offcuts.length];
System.arraycopy( refs, 0, all, 0, refs.length);
Address[] all = new Address[refs.length + offcuts.length];
System.arraycopy(refs, 0, all, 0, refs.length);
System.arraycopy(offcuts, 0, all, refs.length, offcuts.length);
if (displayRefHeader) {
@ -224,4 +221,51 @@ class ReferenceLineDispenser extends AbstractLineDispenser {
buf.delete(0, buf.length());
}
}
public static Address[] getXRefList(CodeUnit cu) {
Program prog = cu.getProgram();
if (prog == null) {
return new Address[0];
}
List<Address> xrefList = new ArrayList<>();
//lookup the direct xrefs to the current code unit
//
ReferenceIterator iter = prog.getReferenceManager().getReferencesTo(cu.getMinAddress());
while (iter.hasNext()) {
Reference ref = iter.next();
xrefList.add(ref.getFromAddress());
}
Address[] arr = new Address[xrefList.size()];
xrefList.toArray(arr);
Arrays.sort(arr);
return arr;
}
private static Address[] getOffcutXRefList(CodeUnit cu) {
Program prog = cu.getProgram();
if (prog == null) {
return new Address[0];
}
List<Address> offcutList = new ArrayList<>();
// Lookup the offcut xrefs...
//
if (cu.getLength() > 1) {
ReferenceManager refMgr = prog.getReferenceManager();
AddressSet set =
new AddressSet(cu.getMinAddress().add(1), cu.getMaxAddress());
AddressIterator iter = refMgr.getReferenceDestinationIterator(set, true);
while (iter.hasNext()) {
Address addr = iter.next();
ReferenceIterator refIter = refMgr.getReferencesTo(addr);
while (refIter.hasNext()) {
Reference ref = refIter.next();
offcutList.add(ref.getFromAddress());
}
}
}
Address[] arr = new Address[offcutList.size()];
offcutList.toArray(arr);
Arrays.sort(arr);
return arr;
}
}

View file

@ -15,18 +15,16 @@
*/
package ghidra.app.util.viewer.field;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.proxy.EmptyProxy;
import ghidra.app.util.viewer.proxy.ProxyObj;
import java.awt.*;
import javax.swing.JComponent;
import docking.widgets.fieldpanel.internal.FieldBackgroundColorManager;
import docking.widgets.fieldpanel.internal.PaintContext;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.RowColLocation;
import docking.widgets.fieldpanel.support.*;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.proxy.EmptyProxy;
import ghidra.app.util.viewer.proxy.ProxyObj;
/**
* Field responsible for drawing +/- symbols when over an aggregate datatype that
@ -177,7 +175,8 @@ public class IndentField implements ListingField {
@Override
public void paint(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc, int rowHeight) {
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc,
int rowHeight) {
g.setColor(Color.LIGHT_GRAY);
// draw the vertical lines to the left of the data (these are shown when there are vertical
@ -228,6 +227,11 @@ public class IndentField implements ListingField {
return true;
}
@Override
public int getNumDataRows() {
return 1;
}
@Override
public int getNumRows() {
return 1;
@ -307,7 +311,7 @@ public class IndentField implements ListingField {
@Override
public RowColLocation textOffsetToScreenLocation(int textOffset) {
return new RowColLocation(0, 0);
return new DefaultRowColLocation();
}
@Override

View file

@ -17,6 +17,8 @@ package ghidra.app.util.viewer.field;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Arrays;
import java.util.List;
import javax.swing.JComponent;
@ -34,7 +36,7 @@ import ghidra.app.util.viewer.proxy.ProxyObj;
*/
public class ListingTextField implements ListingField, TextField {
private ProxyObj proxy;
private ProxyObj<?> proxy;
private FieldFactory factory;
protected TextField field;
@ -47,9 +49,11 @@ public class ListingTextField implements ListingField, TextField {
* @param startX the starting X position of the field
* @param width the width of the field
* @param provider the highlight provider.
* @return the text field.
*/
public static ListingTextField createSingleLineTextField(FieldFactory factory, ProxyObj proxy,
FieldElement fieldElement, int startX, int width, HighlightProvider provider) {
public static ListingTextField createSingleLineTextField(FieldFactory factory,
ProxyObj<?> proxy, FieldElement fieldElement, int startX, int width,
HighlightProvider provider) {
HighlightFactory hlFactory =
new FieldHighlightFactory(provider, factory.getClass(), proxy.getObject());
@ -58,7 +62,7 @@ public class ListingTextField implements ListingField, TextField {
}
public static ListingTextField createSingleLineTextFieldWithReverseClipping(
AddressFieldFactory factory, ProxyObj proxy, FieldElement fieldElement, int startX,
AddressFieldFactory factory, ProxyObj<?> proxy, FieldElement fieldElement, int startX,
int width, HighlightProvider provider) {
HighlightFactory hlFactory =
new FieldHighlightFactory(provider, factory.getClass(), proxy.getObject());
@ -77,9 +81,10 @@ public class ListingTextField implements ListingField, TextField {
* @param width the width of the field
* @param maxLines the maxLines to display.
* @param provider the highlight provider.
* @return the text field.
*/
public static ListingTextField createWordWrappedTextField(FieldFactory factory, ProxyObj proxy,
FieldElement fieldElement, int startX, int width, int maxLines,
public static ListingTextField createWordWrappedTextField(FieldFactory factory,
ProxyObj<?> proxy, FieldElement fieldElement, int startX, int width, int maxLines,
HighlightProvider provider) {
HighlightFactory hlFactory =
@ -100,14 +105,16 @@ public class ListingTextField implements ListingField, TextField {
* @param width the width of the field
* @param maxLines the maxLines to display.
* @param provider the highlight provider.
* @return the text field.
*/
public static ListingTextField createPackedTextField(FieldFactory factory, ProxyObj proxy,
public static ListingTextField createPackedTextField(FieldFactory factory, ProxyObj<?> proxy,
FieldElement[] textElements, int startX, int width, int maxLines,
HighlightProvider provider) {
HighlightFactory hlFactory =
new FieldHighlightFactory(provider, factory.getClass(), proxy.getObject());
TextField field = new FlowLayoutTextField(textElements, startX, width, maxLines, hlFactory);
List<FieldElement> list = Arrays.asList(textElements);
TextField field = new FlowLayoutTextField(list, startX, width, maxLines, hlFactory);
return new ListingTextField(factory, proxy, field);
}
@ -118,22 +125,24 @@ public class ListingTextField implements ListingField, TextField {
* @param textElements the array of elements for the field.
* Each of these holds text, attributes and location information.
* @param startX the starting X position of the field
* @param width the widht of the field
* @param width the width of the field
* @param maxLines the maxLines to display.
* @param provider the highlight provider
* @return the text field.
*/
public static ListingTextField createMultilineTextField(FieldFactory factory, ProxyObj proxy,
public static ListingTextField createMultilineTextField(FieldFactory factory, ProxyObj<?> proxy,
FieldElement[] textElements, int startX, int width, int maxLines,
HighlightProvider provider) {
HighlightFactory hlFactory =
new FieldHighlightFactory(provider, factory.getClass(), proxy.getObject());
List<FieldElement> list = Arrays.asList(textElements);
TextField field =
new VerticalLayoutTextField(textElements, startX, width, maxLines, hlFactory);
new VerticalLayoutTextField(list, startX, width, maxLines, hlFactory);
return new ListingTextField(factory, proxy, field);
}
protected ListingTextField(FieldFactory factory, ProxyObj proxy, TextField field) {
protected ListingTextField(FieldFactory factory, ProxyObj<?> proxy, TextField field) {
this.factory = factory;
this.proxy = proxy;
this.field = field;
@ -186,7 +195,8 @@ public class ListingTextField implements ListingField, TextField {
@Override
public void paint(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc, int rowHeight) {
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc,
int rowHeight) {
field.paint(c, g, context, clip, map, cursorLoc, rowHeight);
}
@ -195,6 +205,11 @@ public class ListingTextField implements ListingField, TextField {
return field.contains(x, y);
}
@Override
public int getNumDataRows() {
return field.getNumDataRows();
}
@Override
public int getNumRows() {
return field.getNumRows();
@ -281,7 +296,7 @@ public class ListingTextField implements ListingField, TextField {
}
@Override
public ProxyObj getProxy() {
public ProxyObj<?> getProxy() {
if (proxy == null) {
return EmptyProxy.EMPTY_PROXY;
}

View file

@ -33,8 +33,8 @@ public class NamespacePropertyEditor extends PropertyEditorSupport implements Cu
private static final String DISPLAY_LOCAL_NAMESPACE_LABEL = "Display Local Namespace";
private static final String DISPLAY_NON_LOCAL_NAMESPACE_LABEL = "Display Non-local Namespace";
private static final String LOCAL_NAMESPACE_PREFIX_LABEL = "Local namespace prefix";
private static final String DISPLAY_LIBRARY_IN_NAMESPACE_LABEL = "Display library in namespace";
private static final String LOCAL_NAMESPACE_PREFIX_LABEL = "Local Namespace Prefix";
private static final String DISPLAY_LIBRARY_IN_NAMESPACE_LABEL = "Display library in Namespace";
private static final String[] NAMES =
{ DISPLAY_LOCAL_NAMESPACE_LABEL, DISPLAY_NON_LOCAL_NAMESPACE_LABEL,

View file

@ -22,8 +22,7 @@ import javax.swing.JComponent;
import docking.widgets.fieldpanel.internal.FieldBackgroundColorManager;
import docking.widgets.fieldpanel.internal.PaintContext;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.RowColLocation;
import docking.widgets.fieldpanel.support.*;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.proxy.EmptyProxy;
import ghidra.app.util.viewer.proxy.ProxyObj;
@ -153,7 +152,8 @@ public class OpenCloseField implements ListingField {
@Override
public void paint(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc, int rowHeight) {
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc,
int rowHeight) {
// center in the heightAbove area (negative, since 0 is the baseline of text, which is at
// the bottom of the heightAbove)
@ -231,6 +231,11 @@ public class OpenCloseField implements ListingField {
return true;
}
@Override
public int getNumDataRows() {
return 1;
}
@Override
public int getNumRows() {
return 1;
@ -310,7 +315,7 @@ public class OpenCloseField implements ListingField {
@Override
public RowColLocation textOffsetToScreenLocation(int textOffset) {
return new RowColLocation(0, 0);
return new DefaultRowColLocation();
}
@Override

View file

@ -141,26 +141,23 @@ public class PlateFieldFactory extends FieldFactory {
}
CodeUnit cu = (CodeUnit) proxy.getObject();
List<FieldElement> elementList = new ArrayList<>(10);
List<FieldElement> elements = new ArrayList<>(10);
boolean isClipped = false;
String commentText = getCommentText(cu);
if ((commentText == null) || (commentText.isEmpty())) {
generateDefaultPlate(elementList, cu);
generateDefaultPlate(elements, cu);
}
else {
isClipped = generateFormattedPlateComment(elementList, cu);
isClipped = generateFormattedPlateComment(elements, cu);
}
addBlankLines(elementList, cu);
addBlankLines(elements, cu);
if (elementList.size() == 0) {
if (elements.size() == 0) {
// no real or default comment
return null;
}
FieldElement[] fields = new FieldElement[elementList.size()];
elementList.toArray(fields);
if (isNestedDataAtSameAddressAsParent(proxy)) {
// This is data at the same address as the parent, which happens with the first
// element in a structure. We do not want to the plate comment here, but only at the
@ -169,7 +166,7 @@ public class PlateFieldFactory extends FieldFactory {
}
PlateFieldTextField textField =
new PlateFieldTextField(fields, this, proxy, startX, width, commentText, isClipped);
new PlateFieldTextField(elements, this, proxy, startX, width, commentText, isClipped);
return new PlateListingTextField(proxy, textField);
}
@ -706,7 +703,7 @@ public class PlateFieldFactory extends FieldFactory {
private boolean isCommentClipped;
private String commentText;
public PlateFieldTextField(FieldElement[] textElements, PlateFieldFactory factory,
public PlateFieldTextField(List<FieldElement> textElements, PlateFieldFactory factory,
ProxyObj<?> proxy, int startX, int width, String commentText,
boolean isCommentClipped) {
super(textElements, startX, width, Integer.MAX_VALUE,

View file

@ -23,7 +23,7 @@ import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.RowColLocation;
import ghidra.app.util.HighlightProvider;
import ghidra.app.util.XReferenceUtil;
import ghidra.app.util.XReferenceUtils;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.proxy.ProxyObj;
import ghidra.framework.options.Options;
@ -36,8 +36,6 @@ import ghidra.program.util.VariableXRefFieldLocation;
/**
* Variable Cross-reference Field Factory
* <br>
*
*/
public class VariableXRefFieldFactory extends XRefFieldFactory {
@ -76,9 +74,6 @@ public class VariableXRefFieldFactory extends XRefFieldFactory {
initDisplayOptions();
}
/**
* @see ghidra.app.util.viewer.field.FieldFactory#getField(ProxyObj, int)
*/
@Override
public ListingField getField(ProxyObj<?> proxy, int varWidth) {
Object obj = proxy.getObject();
@ -89,7 +84,7 @@ public class VariableXRefFieldFactory extends XRefFieldFactory {
Variable var = (Variable) obj;
List<Reference> xrefs = new ArrayList<>();
List<Reference> offcuts = new ArrayList<>();
XReferenceUtil.getVariableRefs(var, xrefs, offcuts);
XReferenceUtils.getVariableRefs(var, xrefs, offcuts, maxXRefs);
if (xrefs.size() + offcuts.size() == 0) {
return null;
@ -164,9 +159,6 @@ public class VariableXRefFieldFactory extends XRefFieldFactory {
width, maxXRefs, hlProvider);
}
/**
* @see ghidra.app.util.viewer.field.FieldFactory#getFieldLocation(ghidra.app.util.viewer.field.ListingField, BigInteger, int, ghidra.program.util.ProgramLocation)
*/
@Override
public FieldLocation getFieldLocation(ListingField bf, BigInteger index, int fieldNum,
ProgramLocation loc) {
@ -188,9 +180,6 @@ public class VariableXRefFieldFactory extends XRefFieldFactory {
return null;
}
/**
* @see ghidra.app.util.viewer.field.FieldFactory#getProgramLocation(int, int, ghidra.app.util.viewer.field.ListingField)
*/
@Override
public ProgramLocation getProgramLocation(int row, int col, ListingField bf) {
Object obj = bf.getProxy().getObject();
@ -207,7 +196,7 @@ public class VariableXRefFieldFactory extends XRefFieldFactory {
Variable var = (Variable) obj;
List<Reference> xrefs = new ArrayList<>();
List<Reference> offcuts = new ArrayList<>();
XReferenceUtil.getVariableRefs(var, xrefs, offcuts);
XReferenceUtils.getVariableRefs(var, xrefs, offcuts, maxXRefs);
Reference ref = null;
if (index < xrefs.size()) {
@ -225,9 +214,6 @@ public class VariableXRefFieldFactory extends XRefFieldFactory {
return null;
}
/**
* @see ghidra.app.util.viewer.field.FieldFactory#acceptsType(int, java.lang.Class)
*/
@Override
public boolean acceptsType(int category, Class<?> proxyObjectClass) {
if (!Variable.class.isAssignableFrom(proxyObjectClass)) {
@ -239,7 +225,7 @@ public class VariableXRefFieldFactory extends XRefFieldFactory {
@Override
public FieldFactory newInstance(FieldFormatModel formatModel, HighlightProvider provider,
ToolOptions displayOptions, ToolOptions fieldOptions) {
return new VariableXRefFieldFactory(formatModel, provider, displayOptions, fieldOptions);
ToolOptions options, ToolOptions fieldOptions) {
return new VariableXRefFieldFactory(formatModel, provider, options, fieldOptions);
}
}

View file

@ -15,16 +15,18 @@
*/
package ghidra.app.util.viewer.field;
import java.util.HashSet;
import java.util.Set;
import ghidra.app.nav.Navigatable;
import ghidra.app.util.XReferenceUtil;
import ghidra.app.util.XReferenceUtils;
import ghidra.app.util.query.TableService;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.Variable;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceManager;
import ghidra.program.util.*;
/**
@ -79,7 +81,24 @@ public class VariableXRefFieldMouseHandler extends XRefFieldMouseHandler {
VariableLocation variableLocation = (VariableLocation) location;
Variable variable = variableLocation.getVariable();
Set<Reference> refs = XReferenceUtil.getVariableRefs(variable);
XReferenceUtil.showAllXrefs(navigatable, serviceProvider, service, location, refs);
Set<Reference> refs = getVariableRefs(variable);
XReferenceUtils.showXrefs(navigatable, serviceProvider, service, location, refs);
}
private Set<Reference> getVariableRefs(Variable var) {
Set<Reference> results = new HashSet<>();
Address addr = var.getMinAddress();
if (addr == null) {
return results;
}
Program program = var.getFunction().getProgram();
ReferenceManager refMgr = program.getReferenceManager();
Reference[] refs = refMgr.getReferencesTo(var);
for (Reference vref : refs) {
results.add(vref);
}
return results;
}
}

View file

@ -16,18 +16,19 @@
package ghidra.app.util.viewer.field;
import java.awt.Color;
import java.awt.FontMetrics;
import java.beans.PropertyEditor;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Comparator;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Predicate;
import javax.swing.event.ChangeListener;
import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.RowColLocation;
import docking.widgets.fieldpanel.support.*;
import ghidra.app.util.HighlightProvider;
import ghidra.app.util.XReferenceUtil;
import ghidra.app.util.XReferenceUtils;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.options.OptionsGui;
import ghidra.app.util.viewer.proxy.ProxyObj;
@ -41,6 +42,7 @@ import ghidra.program.util.ProgramLocation;
import ghidra.program.util.XRefFieldLocation;
import ghidra.util.HelpLocation;
import ghidra.util.exception.AssertException;
import util.CollectionUtils;
/**
* Cross-reference Field Factory
@ -58,17 +60,19 @@ public class XRefFieldFactory extends FieldFactory {
protected SORT_CHOICE sortChoice = SORT_CHOICE.Address;
private static final String GROUP_TITLE = "XREFs Field";
private static final String DELIMITER_MSG = GROUP_TITLE + Options.DELIMITER + "Delimiter";
private static final String MAX_XREFS_MSG =
private static final String DELIMITER_KEY = GROUP_TITLE + Options.DELIMITER + "Delimiter";
static final String MAX_XREFS_KEY =
GROUP_TITLE + Options.DELIMITER + "Maximum Number of XREFs to Display";
private static final String DISPLAY_BLOCK_NAME_MSG =
private static final String DISPLAY_BLOCK_NAME_KEY =
GROUP_TITLE + Options.DELIMITER + "Display Local Block";
private static final String SORT_OPTION =
GROUP_TITLE + Options.DELIMITER + "Sort References By";
private static final String DISPLAY_REFERENCE_TYPE_MSG =
private static final String SORT_OPTION_KEY =
GROUP_TITLE + Options.DELIMITER + "Sort References by";
private static final String DISPLAY_REFERENCE_TYPE_KEY =
GROUP_TITLE + Options.DELIMITER + "Display Reference Type";
private final static String NAMESPACE_OPTIONS =
private final static String NAMESPACE_OPTIONS_KEY =
GROUP_TITLE + Options.DELIMITER + "Display Namespace";
static final String GROUP_BY_FUNCTION_KEY =
GROUP_TITLE + Options.DELIMITER + "Group by Function";
private PropertyEditor namespaceOptionsEditor = new NamespacePropertyEditor();
@ -78,7 +82,7 @@ public class XRefFieldFactory extends FieldFactory {
protected Color otherColor;
protected String delim = DELIMITER;
protected boolean displayBlockName;
protected boolean groupByFunction;
protected int maxXRefs = MAX_XREFS;
protected boolean displayRefType = true;
protected Comparator<Reference> typeComparator;
@ -90,9 +94,6 @@ public class XRefFieldFactory extends FieldFactory {
private BrowserCodeUnitFormat codeUnitFormat;
private ChangeListener codeUnitFormatListener = e -> XRefFieldFactory.this.model.update();
/**
* Constructor
*/
public XRefFieldFactory() {
this(FIELD_NAME);
}
@ -128,15 +129,18 @@ public class XRefFieldFactory extends FieldFactory {
super(name, model, hlProvider, displayOptions, fieldOptions);
HelpLocation hl = new HelpLocation("CodeBrowserPlugin", "XREFs_Field");
fieldOptions.registerOption(DELIMITER_MSG, DELIMITER, hl,
fieldOptions.registerOption(DELIMITER_KEY, DELIMITER, hl,
"Delimiter string used for separating multiple xrefs.");
fieldOptions.registerOption(DISPLAY_BLOCK_NAME_MSG, false, hl,
fieldOptions.registerOption(DISPLAY_BLOCK_NAME_KEY, false, hl,
"Prepends xref addresses with the " +
"name of the memory block containing the xref address.");
fieldOptions.registerOption(MAX_XREFS_MSG, MAX_XREFS, hl,
fieldOptions.registerOption(MAX_XREFS_KEY, MAX_XREFS, hl,
"Sets the maximum number of xrefs to display.");
fieldOptions.registerOption(DISPLAY_REFERENCE_TYPE_MSG, true, hl, "Appends xref type.");
fieldOptions.registerOption(SORT_OPTION, SORT_CHOICE.Address, hl, "How to sort the xrefs");
fieldOptions.registerOption(DISPLAY_REFERENCE_TYPE_KEY, true, hl, "Appends xref type.");
fieldOptions.registerOption(SORT_OPTION_KEY, SORT_CHOICE.Address, hl,
"How to sort the xrefs");
fieldOptions.registerOption(GROUP_BY_FUNCTION_KEY, false, hl,
"True signals to group all xrefs by the containing calling function.");
offcutColor = displayOptions.getColor(OptionsGui.XREF_OFFCUT.getColorOptionName(),
OptionsGui.XREF_OFFCUT.getDefaultColor());
@ -154,12 +158,13 @@ public class XRefFieldFactory extends FieldFactory {
return r1.getReferenceType().toString().compareTo(r2.getReferenceType().toString());
};
delim = fieldOptions.getString(DELIMITER_MSG, DELIMITER);
displayBlockName = fieldOptions.getBoolean(DISPLAY_BLOCK_NAME_MSG, false);
delim = fieldOptions.getString(DELIMITER_KEY, DELIMITER);
displayBlockName = fieldOptions.getBoolean(DISPLAY_BLOCK_NAME_KEY, false);
maxXRefs = fieldOptions.getInt(MAX_XREFS_MSG, MAX_XREFS);
sortChoice = fieldOptions.getEnum(SORT_OPTION, SORT_CHOICE.Address);
displayRefType = fieldOptions.getBoolean(DISPLAY_REFERENCE_TYPE_MSG, true);
maxXRefs = fieldOptions.getInt(MAX_XREFS_KEY, MAX_XREFS);
sortChoice = fieldOptions.getEnum(SORT_OPTION_KEY, SORT_CHOICE.Address);
displayRefType = fieldOptions.getBoolean(DISPLAY_REFERENCE_TYPE_KEY, true);
groupByFunction = fieldOptions.getBoolean(GROUP_BY_FUNCTION_KEY, false);
fieldOptions.getOptions(GROUP_TITLE).setOptionsHelpLocation(hl);
@ -172,15 +177,17 @@ public class XRefFieldFactory extends FieldFactory {
private void setupNamespaceOptions(Options fieldOptions) {
// we need to install a custom editor that allows us to edit a group of related options
fieldOptions.registerOption(NAMESPACE_OPTIONS, OptionType.CUSTOM_TYPE,
fieldOptions.registerOption(NAMESPACE_OPTIONS_KEY, OptionType.CUSTOM_TYPE,
new NamespaceWrappedOption(), null, "Adjusts the XREFs Field namespace display",
namespaceOptionsEditor);
CustomOption customOption = fieldOptions.getCustomOption(NAMESPACE_OPTIONS, null);
fieldOptions.getOptions(NAMESPACE_OPTIONS).setOptionsHelpLocation(
CustomOption customOption = fieldOptions.getCustomOption(NAMESPACE_OPTIONS_KEY, null);
fieldOptions.getOptions(NAMESPACE_OPTIONS_KEY)
.setOptionsHelpLocation(
new HelpLocation("CodeBrowserPlugin", "XREFs_Field"));
if (!(customOption instanceof NamespaceWrappedOption)) {
throw new AssertException(
"Someone set an option for " + NAMESPACE_OPTIONS + " that is not the expected " +
"Someone set an option for " + NAMESPACE_OPTIONS_KEY +
" that is not the expected " +
"ghidra.app.util.viewer.field.NamespaceWrappedOption type.");
}
@ -222,33 +229,40 @@ public class XRefFieldFactory extends FieldFactory {
public void fieldOptionsChanged(Options options, String optionName, Object oldValue,
Object newValue) {
super.fieldOptionsChanged(options, optionName, oldValue, newValue);
if (optionName.equals(DELIMITER_MSG)) {
if (optionName.equals(DELIMITER_KEY)) {
delim = (String) newValue;
model.update();
}
else if (optionName.equals(DISPLAY_BLOCK_NAME_MSG)) {
displayBlockName = ((Boolean) newValue).booleanValue();
else if (optionName.equals(DISPLAY_BLOCK_NAME_KEY)) {
displayBlockName = (Boolean) newValue;
model.update();
}
else if (optionName.equals(DISPLAY_REFERENCE_TYPE_MSG)) {
displayRefType = ((Boolean) newValue).booleanValue();
else if (optionName.equals(DISPLAY_REFERENCE_TYPE_KEY)) {
displayRefType = (Boolean) newValue;
model.update();
}
else if (optionName.equals(MAX_XREFS_MSG)) {
else if (optionName.equals(MAX_XREFS_KEY)) {
setMaxSize(((Integer) newValue).intValue(), options);
model.update();
}
else if (optionName.equals(SORT_OPTION)) {
else if (optionName.equals(SORT_OPTION_KEY)) {
sortChoice = (SORT_CHOICE) newValue;
model.update();
}
else if (optionName.equals(NAMESPACE_OPTIONS)) {
else if (optionName.equals(NAMESPACE_OPTIONS_KEY)) {
setupNamespaceOptions(options);
model.update();
}
else if (optionName.equals(GROUP_BY_FUNCTION_KEY)) {
groupByFunction = (Boolean) newValue;
model.update();
}
}
private void setMaxSize(int n, Options options) {
if (n < 1) {
n = 1;
options.setInt(MAX_XREFS_MSG, 1);
options.setInt(MAX_XREFS_KEY, 1);
}
maxXRefs = n;
}
@ -265,87 +279,362 @@ public class XRefFieldFactory extends FieldFactory {
return null;
}
if (obj == null || !(obj instanceof CodeUnit)) {
if (!(obj instanceof CodeUnit)) {
return null;
}
CodeUnit cu = (CodeUnit) obj;
Program pgm = cu.getProgram();
Reference[] xrefs = XReferenceUtil.getXReferences(cu, maxXRefs + 1);
int maxOffcuts = Math.max(0, maxXRefs - xrefs.length);
Reference[] offcuts = XReferenceUtil.getOffcutXReferences(cu, maxOffcuts);
List<Reference> xrefs = XReferenceUtils.getXReferences(cu, maxXRefs + 1);
int maxOffcuts = Math.max(0, maxXRefs - xrefs.size());
List<Reference> offcuts = XReferenceUtils.getOffcutXReferences(cu, maxOffcuts);
if (sortChoice == SORT_CHOICE.Address) {
Arrays.sort(xrefs);
Arrays.sort(offcuts);
xrefs.sort(null);
offcuts.sort(null);
}
else {
Arrays.sort(xrefs, typeComparator);
Arrays.sort(offcuts, typeComparator);
xrefs.sort(typeComparator);
offcuts.sort(typeComparator);
}
int totalXrefs = xrefs.length + offcuts.length;
if (groupByFunction) {
return getFieldByFunction(proxy, varWidth, xrefs, offcuts);
}
return getFieldByAddress(proxy, varWidth, xrefs, offcuts);
}
/*
Create a series of fields: 1 row per function and the xrefs it contains and 1 wrapping
field for all xrefs not in any function. The wrapping field will go below the function
based xrefs. It will look something like this:
foo1: 123, 223
foo2: 323, 333
423, 433, 567,
899, [more]
The fields and elements created by this method have this structure:
XrefListingField
CompositeVerticalLayoutTextField
0+ ClippingTextField
CompositeFieldElement
XrefFieldEleent
XrefAttributedString
0+ FlowLayoutTextField
XrefFieldEleent
XrefAttributedString
*/
private ListingField getFieldByFunction(ProxyObj<?> proxy, int varWidth,
List<Reference> xrefs, List<Reference> offcuts) {
int totalXrefs = xrefs.size() + offcuts.size();
if (totalXrefs == 0) {
return null;
}
boolean tooMany = totalXrefs > maxXRefs;
AttributedString delimiter = new AttributedString(delim, Color.BLACK, getMetrics());
FieldElement[] elements = new FieldElement[tooMany ? maxXRefs + 1 : totalXrefs];
Function currentFunction =
pgm.getFunctionManager().getFunctionContaining(cu.getMinAddress());
int count = 0;
for (; count < xrefs.length && count < elements.length; count++) {
String prefix = getPrefix(pgm, xrefs[count], currentFunction);
String addressString = xrefs[count].getFromAddress().toString(prefix);
AttributedString as = new AttributedString(addressString, color, getMetrics());
if (displayRefType) {
as = createRefTypeAttributedString(xrefs[count], as);
}
if (count < totalXrefs - 1) {
as = new CompositeAttributedString(new AttributedString[] { as, delimiter });
Object obj = proxy.getObject();
CodeUnit cu = (CodeUnit) obj;
Program program = cu.getProgram();
FontMetrics metrics = getMetrics();
FunctionManager functionManager = program.getFunctionManager();
//
// Bin all xrefs by containing function, which may be null
//
List<Reference> noFunction = new ArrayList<>();
TreeMap<Function, List<Reference>> xrefsByFunction = new TreeMap<>((f1, f2) -> {
return f1.getEntryPoint().compareTo(f2.getEntryPoint());
});
for (Reference ref : CollectionUtils.asIterable(xrefs, offcuts)) {
Function function = functionManager.getFunctionContaining(ref.getFromAddress());
if (function == null) {
noFunction.add(ref);
}
else {
// This added to prevent a situation where resizing field to a particular size,
// resulted in layout of references to be strange
char[] charSpaces = new char[delimiter.length()];
Arrays.fill(charSpaces, ' ');
AttributedString spaces =
new AttributedString(new String(charSpaces), color, getMetrics());
as = new CompositeAttributedString(new AttributedString[] { as, spaces });
xrefsByFunction.computeIfAbsent(function, r -> new ArrayList<>()).add(ref);
}
elements[count] = new TextFieldElement(as, count, 0);
}
for (int i = 0; i < offcuts.length && count < elements.length; i++, count++) {
String prefix = getPrefix(pgm, offcuts[i], currentFunction);
String addressString = offcuts[i].getFromAddress().toString(prefix);
AttributedString as = new AttributedString(addressString, offcutColor, getMetrics());
if (displayRefType) {
as = createRefTypeAttributedString(offcuts[i], as);
}
if (count < totalXrefs - 1) {
as = new CompositeAttributedString(new AttributedString[] { as, delimiter });
}
else {
// This added to prevent a situation where resizing field to a particular size,
// resulted in layout of references to be strange
char[] charSpaces = new char[delimiter.length()];
Arrays.fill(charSpaces, ' ');
AttributedString spaces =
new AttributedString(new String(charSpaces), offcutColor, getMetrics());
as = new CompositeAttributedString(new AttributedString[] { as, spaces });
}
elements[count] = new TextFieldElement(as, count, 0);
}
//
// Create the function rows
//
Set<Reference> offcutSet = new HashSet<>(offcuts);
Predicate<Reference> isOffcut = r -> offcutSet.contains(r);
HighlightFactory hlFactory =
new FieldHighlightFactory(hlProvider, getClass(), proxy.getObject());
Function currentFunction = functionManager.getFunctionContaining(cu.getMinAddress());
List<TextField> functionRows =
createXrefRowsByFunction(program, currentFunction, xrefsByFunction, isOffcut, varWidth,
hlFactory);
//
// TODO maxXRefs makes sense when simply displaying xrefs. What does max mean when
// binning xrefs by function. Currently, we use the max as the max row count, but
// this may need to be changed and it may require a new tool option.
//
int maxLines = maxXRefs;
int availableLines = maxLines - functionRows.size();
if (tooMany) {
AttributedString as = new AttributedString(MORE_XREFS_STRING, color, getMetrics());
elements[elements.length - 1] = new TextFieldElement(as, count - 1, 0);
// save room for the "more" field at the end
availableLines -= 1;
}
return ListingTextField.createPackedTextField(this, proxy, elements, startX + varWidth,
width, maxXRefs, hlProvider);
//
// Create the row for xrefs not in a function
//
//
// Note: the objects we build here want the 'data' row as a parameter, not the screen row.
// Out screen rows are what we are building to display; a data row we are here
// defining to be a single xref. This is a somewhat arbitrary decision.
int dataRow = totalXrefs - noFunction.size();
TextField noFunctionXrefsField =
createWrappingXrefRow(program, dataRow, noFunction, currentFunction, isOffcut,
availableLines, hlFactory);
List<TextField> allFields = new ArrayList<>();
allFields.addAll(functionRows);
if (noFunctionXrefsField != null) {
allFields.add(noFunctionXrefsField);
}
int newStartX = startX + varWidth;
if (tooMany) {
// add the [more] element
int lastRow = allFields.size() - 1;
AttributedString as = new AttributedString(MORE_XREFS_STRING, color, metrics);
TextFieldElement moreElement = new TextFieldElement(as, lastRow, 0);
ClippingTextField ctf = new ClippingTextField(newStartX, width, moreElement, hlFactory);
allFields.add(ctf);
}
CompositeVerticalLayoutTextField compositefield =
new CompositeVerticalLayoutTextField(allFields, newStartX, width, maxXRefs, hlFactory);
return new XrefListingField(this, proxy, compositefield);
}
private List<TextField> createXrefRowsByFunction(Program program, Function currentFunction,
TreeMap<Function, List<Reference>> xrefsByFunction, Predicate<Reference> isOffcut,
int varWidth,
HighlightFactory hlFactory) {
FontMetrics metrics = getMetrics();
AttributedString delimiter = new AttributedString(delim, Color.BLACK, metrics);
int row = 0;
List<FieldElement> elements = new ArrayList<>();
Set<Entry<Function, List<Reference>>> entries = xrefsByFunction.entrySet();
for (Entry<Function, List<Reference>> entry : entries) {
//
// Example row: functionName: 1234(c), 1238(c)
//
List<Reference> refs = entry.getValue();
Function fromFunction = entry.getKey();
String functionName = fromFunction.getName();
int refCount = refs.size();
String sizeText = ": ";
if (refCount > 1) {
sizeText = "[" + refs.size() + "]: ";
}
String text = functionName + sizeText;
AttributedString nameString =
new AttributedString(text, color, metrics);
List<XrefFieldElement> rowElements = new ArrayList<>();
Reference firstRef = refs.get(0);
XrefAttributedString xrefString =
new XrefAttributedString(firstRef, nameString);
rowElements.add(new XrefFieldElement(xrefString, row, 0));
//
// TODO how many xrefs to display per function?
//
int n = Math.min(10, refs.size());
for (int i = 0; i < n; i++) {
boolean isLast = i == n - 1;
Reference ref = refs.get(i);
String prefix = getMergedPrefix(program, ref, currentFunction, fromFunction);
XrefFieldElement element =
createFunctionElement(program, prefix, ref, row, isLast ? null : delimiter,
isOffcut.test(ref));
rowElements.add(element);
}
elements.add(new CompositeFieldElement(rowElements));
row++;
}
int newStartX = startX + varWidth;
List<TextField> textFields = new ArrayList<>();
for (FieldElement element : elements) {
textFields.add(new ClippingTextField(newStartX, width, element, hlFactory));
}
return textFields;
}
private TextField createWrappingXrefRow(Program program, int startRow, List<Reference> xrefs,
Function currentFunction, Predicate<Reference> isOffcut, int availableLines,
HighlightFactory hlFactory) {
FontMetrics metrics = getMetrics();
AttributedString delimiter = new AttributedString(delim, Color.BLACK, metrics);
int row = startRow;
List<XrefFieldElement> elements = new ArrayList<>();
for (Reference ref : xrefs) {
String prefix = getPrefix(program, ref, currentFunction, null);
XrefFieldElement element =
createReferenceElement(program, prefix, ref, row, delimiter, isOffcut.test(ref));
elements.add(element);
row++;
}
// add all elements to a field that will wrap as needed
if (!elements.isEmpty()) {
List<FieldElement> fieldElements = toFieldElements(elements, false);
return new FlowLayoutTextField(fieldElements, startX, width, availableLines, hlFactory);
}
return null;
}
/*
Create a series of fields: 1 row per function and the xrefs it contains and 1 wrapping
field for all xrefs not in any function. The wrapping field will go below the function
based xrefs. It will look something like this:
foo1:423,
foo1:433,
foo2:567,
899, [more]
The fields and elements created by this method have this structure:
XrefListingField
1+ FlowLayoutTextField
XrefFieldEleent
XrefAttributedString
*/
private ListingField getFieldByAddress(ProxyObj<?> proxy, int varWidth, List<Reference> xrefs,
List<Reference> offcuts) {
int totalXrefs = xrefs.size() + offcuts.size();
if (totalXrefs == 0) {
return null;
}
Object obj = proxy.getObject();
CodeUnit cu = (CodeUnit) obj;
Program program = cu.getProgram();
FontMetrics metrics = getMetrics();
AttributedString delimiter = new AttributedString(delim, Color.BLACK, metrics);
Set<Reference> offcutSet = new HashSet<>(offcuts);
Predicate<Reference> isOffcut = r -> offcutSet.contains(r);
boolean tooMany = totalXrefs > maxXRefs;
List<XrefFieldElement> elements = new ArrayList<>();
FunctionManager functionManager = program.getFunctionManager();
Function currentFunction = functionManager.getFunctionContaining(cu.getMinAddress());
int n = tooMany ? maxXRefs + 1 : totalXrefs;
int count = 0;
for (; count < xrefs.size() && count < n; count++) {
Reference ref = xrefs.get(count);
String prefix = getPrefix(program, ref, currentFunction);
elements.add(
createReferenceElement(program, prefix, ref, count, delimiter, isOffcut.test(ref)));
}
for (int i = 0; i < offcuts.size() && count < n; i++, count++) {
Reference ref = offcuts.get(i);
String prefix = getPrefix(program, ref, currentFunction);
elements.add(
createReferenceElement(program, prefix, ref, count, delimiter, isOffcut.test(ref)));
}
if (!tooMany) {
XrefFieldElement lastElement = elements.get(elements.size() - 1);
lastElement.hideDelimiter();
}
List<FieldElement> fieldElements = toFieldElements(elements, tooMany);
return createPackedTextField(proxy, varWidth, fieldElements);
}
// note: this method was inspired by ListingTextField.createPackedTextField()
private XrefListingField createPackedTextField(ProxyObj<?> proxy, int varWidth,
List<FieldElement> list) {
// assumption: the given array has been limited to the maxXref size already
int n = list.size();
HighlightFactory hlFactory =
new FieldHighlightFactory(hlProvider, getClass(), proxy.getObject());
TextField field =
new FlowLayoutTextField(list, startX + varWidth, width, n, hlFactory);
return new XrefListingField(this, proxy, field);
}
private List<FieldElement> toFieldElements(List<XrefFieldElement> list, boolean showEllipses) {
List<FieldElement> fieldElements = new ArrayList<>(list);
if (showEllipses) {
// add the 'more' string
int lastRow = list.size() - 1;
AttributedString as = new AttributedString(MORE_XREFS_STRING, color, getMetrics());
fieldElements.add(new TextFieldElement(as, lastRow, 0));
}
return fieldElements;
}
private XrefFieldElement createFunctionElement(Program program, String prefix, Reference ref,
int row, AttributedString delimiter, boolean isOffcut) {
FontMetrics metrics = getMetrics();
String addressString = ref.getFromAddress().toString(prefix);
Color refColor = isOffcut ? offcutColor : color;
AttributedString addressPart = new AttributedString(addressString, refColor, metrics);
if (displayRefType) {
addressPart = createRefTypeAttributedString(ref, addressPart);
}
XrefAttributedString xrefString =
new XrefAttributedString(ref, addressPart, delimiter);
if (delimiter == null) {
xrefString.hideDelimiter();
}
return new XrefFieldElement(xrefString, row, 0);
}
private XrefFieldElement createReferenceElement(Program program, String prefix, Reference ref,
int row, AttributedString delimiter, boolean isOffcut) {
FontMetrics metrics = getMetrics();
String addressString = ref.getFromAddress().toString(prefix);
Color refColor = isOffcut ? offcutColor : color;
AttributedString as = new AttributedString(addressString, refColor, metrics);
if (displayRefType) {
as = createRefTypeAttributedString(ref, as);
}
XrefAttributedString xrefString =
new XrefAttributedString(ref, as, delimiter);
return new XrefFieldElement(xrefString, row, 0);
}
protected AttributedString createRefTypeAttributedString(Reference reference,
@ -378,8 +667,16 @@ public class XRefFieldFactory extends FieldFactory {
}
protected String getPrefix(Program program, Reference reference, Function currentFunction) {
String prefix = "";
Address fromAddress = reference.getFromAddress();
Function fromFunction = program.getListing().getFunctionContaining(fromAddress);
return getPrefix(program, reference, currentFunction, fromFunction);
}
private String getMergedPrefix(Program program, Reference reference, Function currentFunction,
Function fromFunction) {
String prefix = "";
Address fromAddress = reference.getFromAddress();
if (displayBlockName) {
prefix = getBlockName(program, fromAddress) + ":";
@ -389,15 +686,34 @@ public class XRefFieldFactory extends FieldFactory {
return prefix; // no namespaces being shown
}
Function refFunction = program.getListing().getFunctionContaining(fromAddress);
if (refFunction == null) {
boolean isLocal = Objects.equals(currentFunction, fromFunction);
if (isLocal && useLocalPrefixOverride) {
return prefix + localPrefixText;
}
return prefix;
}
boolean isLocal = refFunction.equals(currentFunction);
private String getPrefix(Program program, Reference reference, Function currentFunction,
Function fromFunction) {
String prefix = "";
Address fromAddress = reference.getFromAddress();
if (displayBlockName) {
prefix = getBlockName(program, fromAddress) + ":";
}
if (!displayLocalNamespace && !displayNonLocalNamespace) {
return prefix; // no namespaces being shown
}
if (fromFunction == null) {
return prefix;
}
boolean isLocal = fromFunction.equals(currentFunction);
if (!isLocal) {
if (displayNonLocalNamespace) {
return prefix + refFunction.getName() + ":";
return prefix + fromFunction.getName() + ":";
}
return prefix; // this means different function, but not displaying other namespaces
}
@ -412,7 +728,6 @@ public class XRefFieldFactory extends FieldFactory {
return prefix + localPrefixText;
}
return prefix + currentFunction.getName() + ":";
}
private String getRefTypeDisplayString(Reference reference) {
@ -460,60 +775,45 @@ public class XRefFieldFactory extends FieldFactory {
protected FieldLocation createFieldLocation(int xrefPos, int xrefIndex, ListingTextField field,
BigInteger index, int fieldNum) {
RowColLocation loc = field.dataToScreenLocation(xrefIndex, xrefPos);
return new FieldLocation(index, fieldNum, loc.row(), loc.col());
}
@Override
public ProgramLocation getProgramLocation(int row, int col, ListingField bf) {
Object obj = bf.getProxy().getObject();
public ProgramLocation getProgramLocation(int row, int col, ListingField listingField) {
Object obj = listingField.getProxy().getObject();
if (obj == null || !(obj instanceof CodeUnit)) {
return null;
}
CodeUnit cu = (CodeUnit) obj;
ListingTextField field = (ListingTextField) getField(bf.getProxy(), 0);
if (field == null) {
if (!(listingField instanceof XrefListingField)) {
return null;
}
RowColLocation loc = field.screenToDataLocation(row, col);
int index = loc.row();
Reference[] xrefs = XReferenceUtil.getXReferences(cu, maxXRefs + 1);
if (sortChoice == SORT_CHOICE.Address) {
Arrays.sort(xrefs);
}
else {
Arrays.sort(xrefs, typeComparator);
}
CodeUnit cu = (CodeUnit) obj;
Address refAddr = null;
if (index < xrefs.length) {
refAddr = xrefs[index].getFromAddress();
}
else {
Reference[] offcuts = XReferenceUtil.getOffcutXReferences(cu, maxXRefs);
if (sortChoice == SORT_CHOICE.Address) {
Arrays.sort(offcuts);
}
else {
Arrays.sort(offcuts, typeComparator);
}
if (index < xrefs.length + offcuts.length) {
refAddr = offcuts[index - xrefs.length].getFromAddress();
}
}
if (refAddr != null) {
int[] cpath = null;
if (cu instanceof Data) {
cpath = ((Data) cu).getComponentPath();
}
return new XRefFieldLocation(cu.getProgram(), cu.getMinAddress(), cpath, refAddr, index,
XrefListingField field = (XrefListingField) listingField;
FieldElement element = field.getFieldElement(row, col);
RowColLocation loc = field.screenToDataLocation(row, col);
if (element instanceof XrefFieldElement) {
XrefFieldElement xrefElement = (XrefFieldElement) element;
Reference xref = xrefElement.getXref();
Address refAddr = xref.getFromAddress();
return new XRefFieldLocation(cu.getProgram(), cu.getMinAddress(), cpath, refAddr, row,
loc.col());
}
String text = element.getText();
if (MORE_XREFS_STRING.equals(text)) {
return new XRefFieldLocation(cu.getProgram(), cu.getMinAddress(), cpath, null, row,
loc.col());
}
return null;
}
@ -561,4 +861,77 @@ public class XRefFieldFactory extends FieldFactory {
ToolOptions toolOptions, ToolOptions fieldOptions) {
return new XRefFieldFactory(formatModel, provider, toolOptions, fieldOptions);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class XrefAttributedString extends CompositeAttributedString {
private AttributedString content;
private AttributedString delimiter;
private Reference xref;
public XrefAttributedString(Reference xref, AttributedString content) {
super(content);
this.content = content;
this.xref = xref;
}
public XrefAttributedString(Reference xref, AttributedString content,
AttributedString delimiter) {
super(content, delimiter);
this.content = content;
this.delimiter = delimiter;
this.xref = xref;
}
void hideDelimiter() {
AttributedString source = delimiter;
if (source == null) {
source = content;
}
int length = delimiter == null ? 1 : delimiter.length();
// Use spaces instead of an empty string; this added to prevent a situation where
// resizing field to a particular size, resulted in layout of references to be strange
char[] charSpaces = new char[length];
Arrays.fill(charSpaces, ' ');
AttributedString spaces =
new AttributedString(new String(charSpaces), source.getColor(0),
source.getFontMetrics(0));
attributedStrings[attributedStrings.length - 1] = spaces;
}
Reference getXref() {
return xref;
}
}
private class XrefFieldElement extends TextFieldElement {
private XrefAttributedString xrefString;
public XrefFieldElement(XrefAttributedString xrefString, int row, int column) {
super(xrefString, row, column);
this.xrefString = xrefString;
}
void hideDelimiter() {
xrefString.hideDelimiter();
}
Reference getXref() {
return xrefString.getXref();
}
}
private class XrefListingField extends ListingTextField {
XrefListingField(XRefFieldFactory factory, ProxyObj<?> proxy, TextField field) {
super(factory, proxy, field);
}
}
}

View file

@ -22,7 +22,7 @@ import docking.widgets.fieldpanel.field.FieldElement;
import docking.widgets.fieldpanel.field.TextField;
import ghidra.app.nav.Navigatable;
import ghidra.app.services.GoToService;
import ghidra.app.util.XReferenceUtil;
import ghidra.app.util.XReferenceUtils;
import ghidra.app.util.query.TableService;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
@ -105,8 +105,8 @@ public class XRefFieldMouseHandler implements FieldMouseHandlerExtension {
return;
}
Set<Reference> refs = XReferenceUtil.getAllXrefs(location);
XReferenceUtil.showAllXrefs(navigatable, serviceProvider, service, location, refs);
Set<Reference> refs = XReferenceUtils.getAllXrefs(location);
XReferenceUtils.showXrefs(navigatable, serviceProvider, service, location, refs);
}
protected ProgramLocation getReferredToLocation(Navigatable sourceNavigatable,

View file

@ -16,17 +16,19 @@
package ghidra.app.util.viewer.field;
import java.math.BigInteger;
import java.util.List;
import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.FieldLocation;
import ghidra.app.util.HighlightProvider;
import ghidra.app.util.XReferenceUtil;
import ghidra.app.util.XReferenceUtils;
import ghidra.app.util.viewer.format.FieldFormatModel;
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.listing.*;
import ghidra.program.model.symbol.Reference;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.XRefHeaderFieldLocation;
@ -126,14 +128,20 @@ public class XRefHeaderFieldFactory extends XRefFieldFactory {
return null;
}
Program prog = cu.getProgram();
int xrefCnt = prog.getReferenceManager().getReferenceCountTo(cu.getMinAddress());
int offcutCnt = XReferenceUtil.getOffcutXRefCount(cu);
int xrefCount = prog.getReferenceManager().getReferenceCountTo(cu.getMinAddress());
List<Reference> offcuts = XReferenceUtils.getOffcutXReferences(cu, maxXRefs);
int offcutCount = offcuts.size();
if (offcutCnt > 0) {
return "XREF[" + xrefCnt + "," + offcutCnt + "]: ";
if (offcutCount > 0) {
String modifier = "";
if (offcutCount == maxXRefs) {
modifier = "+";
}
if (xrefCnt > 0) {
return "XREF[" + xrefCnt + "]: ";
return "XREF[" + xrefCount + "," + offcutCount + modifier + "]: ";
}
if (xrefCount > 0) {
return "XREF[" + xrefCount + "]: ";
}
return null;
}

View file

@ -41,10 +41,6 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
private Options fieldOptions;
private Program program;
public EolCommentFieldFactoryTest() {
super();
}
@Before
public void setUp() throws Exception {
@ -87,7 +83,7 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
//==================================================================================================
private ProgramDB buildProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY, this);
ProgramBuilder builder = new ProgramBuilder("sample", ProgramBuilder._TOY, this);
builder.createMemory(".text", "0x1001000", 0x6600);
builder.createEmptyFunction(null, "0x1002000", 20, null);

View file

@ -0,0 +1,623 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.viewer.field;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.junit.*;
import docking.widgets.fieldpanel.field.FieldElement;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.framework.options.Options;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceManager;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.util.table.GhidraProgramTableModel;
public class XRefFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private ProgramBuilder builder;
private Program program;
private CodeBrowserPlugin cb;
private Options fieldOptions;
private int callerCount;
private int functionWithNoCalls;
private int functionCalledByOneOtherFunction;
private int functionCalledByMultipleFunctions;
private int functionWithAllTypesOfCalls;
private int nonFunctionOffset;
@Before
public void setUp() throws Exception {
program = buildProgram();
env = new TestEnv();
env.launchDefaultTool(program);
cb = env.getPlugin(CodeBrowserPlugin.class);
fieldOptions = cb.getFormatManager().getFieldOptions();
}
@After
public void tearDown() throws Exception {
env.dispose();
}
private ProgramDB buildProgram() throws Exception {
builder = new ProgramBuilder("test", ProgramBuilder._TOY, this);
builder.createMemory(".text", "0x0", 0x100000);
/*
Create a few functions that call other functions
Create some function calls outside of functions
*/
int callerOffset = 0x20000;
Function caller1 = caller(callerOffset);
Function caller2 = caller(callerOffset + 1000);
Function caller3 = caller(callerOffset + 2000);
Function caller4 = caller(callerOffset + 3000);
Function caller5 = caller(callerOffset + 4000);
Function caller6 = caller(callerOffset + 5000);
// function with no calls
functionWithNoCalls = 0x0000;
function(functionWithNoCalls);
// function called by one function once
functionCalledByOneOtherFunction = 0x1000;
function(functionCalledByOneOtherFunction);
createCallerReference(functionCalledByOneOtherFunction, caller1, 1);
// function called by multiple functions multiple times each
functionCalledByMultipleFunctions = 0x2000;
function(functionCalledByMultipleFunctions);
createCallerReference(functionCalledByMultipleFunctions, caller2, 3);
createCallerReference(functionCalledByMultipleFunctions, caller3, 5);
// function called my multiple functions multiple times each and calls from not in functions
functionWithAllTypesOfCalls = 0x3000;
function(functionWithAllTypesOfCalls);
createCallerReference(functionWithAllTypesOfCalls, caller4, 2);
createCallerReference(functionWithAllTypesOfCalls, caller5, 5);
createCallerReference(functionWithAllTypesOfCalls, caller6, 3);
nonFunctionOffset = 0x30000;
createNonFunctionReferences(functionWithAllTypesOfCalls, nonFunctionOffset, 10);
return builder.getProgram();
}
@Test
public void testXrefs_DefaultView() {
/*
XREF[20]: callerFunction4:00020bbc(c),
callerFunction4:00020bc0(c),
callerFunction5:00020fa4(c),
callerFunction5:00020fa8(c),
callerFunction5:00020fac(c),
callerFunction5:00020fb0(c),
callerFunction5:00020fb4(c),
callerFunction6:0002138c(c),
callerFunction6:00021390(c),
callerFunction6:00021394(c),
00030004(c), 00030008(c),
0003000c(c), 00030010(c),
00030014(c), 00030018(c),
0003001c(c), 00030020(c),
00030024(c), 00030028(c)
*/
setGroupByFunctionOption(false);
goToXrefField(functionWithAllTypesOfCalls);
ListingTextField tf = (ListingTextField) cb.getCurrentField();
assertContainsRow(tf, "callerFunction4:00020bbc(c)");
assertContainsRow(tf, "00030004(c), 00030008(c),");
}
@Test
public void testXrefs_GroupByFunctionView_CallsFromInFunctionsOnly() {
/*
XREF[8]: callerFunction2[3]: 000203ec(c),
callerFunction3[5]: 000207d4(c),
*/
setGroupByFunctionOption(true);
goToXrefField(functionCalledByMultipleFunctions);
ListingTextField tf = (ListingTextField) cb.getCurrentField();
assertEquals(2, tf.getNumRows());
assertContainsRow(tf, "callerFunction2[3]: 000203ec(c)");
assertContainsRow(tf, "allerFunction3[5]: 000207d4(c)");
}
@Test
public void testXrefs_GroupByFunctionView_CallsFromInFunctionsAndNotInFunctions() {
/*
XREF[20]: callerFunction4[2]: 00020bbc(c),
callerFunction5[5]: 00020fa4(c),
callerFunction6[3]: 0002138c(c),
00030004(c), 00030008(c),
0003000c(c), 00030010(c),
00030014(c), 00030018(c),
0003001c(c), 00030020(c),
00030024(c), 00030028(c)
*/
setGroupByFunctionOption(true);
goToXrefField(functionWithAllTypesOfCalls);
ListingTextField tf = (ListingTextField) cb.getCurrentField();
assertContainsRow(tf, "callerFunction4[2]: 00020bbc(c)");
assertContainsRow(tf, "callerFunction5[5]: 00020fa4(c)");
assertContainsRow(tf, "00030004(c), 00030008(c),");
}
@Test
public void testXrefs_DefaultView_NoXrefs() {
setGroupByFunctionOption(false);
assertFalse(hasXrefField(functionWithNoCalls));
}
@Test
public void testXrefs_DefaultView_DoubleClickFunctionName() {
/*
XREF[20]: callerFunction4:00020bbc(c),
callerFunction4:00020bc0(c),
callerFunction5:00020fa4(c),
callerFunction5:00020fa8(c),
callerFunction5:00020fac(c),
callerFunction5:00020fb0(c),
callerFunction5:00020fb4(c),
callerFunction6:0002138c(c),
callerFunction6:00021390(c),
callerFunction6:00021394(c),
00030004(c), 00030008(c),
0003000c(c), 00030010(c),
00030014(c), 00030018(c),
0003001c(c), 00030020(c),
00030024(c), 00030028(c)
*/
setGroupByFunctionOption(false);
String callerFunction = "callerFunction4";
goToXrefField(functionWithAllTypesOfCalls, callerFunction);
doubleClick();
assertInFunction(callerFunction);
}
@Test
public void testXrefs_GroupByFunctionView_DoubleClickFunctionName() {
/*
XREF[20]: callerFunction4[2]: 00020bbc(c),
callerFunction5[5]: 00020fa4(c),
callerFunction6[3]: 0002138c(c),
00030004(c), 00030008(c),
0003000c(c), 00030010(c),
00030014(c), 00030018(c),
0003001c(c), 00030020(c),
00030024(c), 00030028(c)
*/
setGroupByFunctionOption(true);
goToXrefField(functionWithAllTypesOfCalls);
String callerFunction = "callerFunction6";
goToXrefField(functionWithAllTypesOfCalls, callerFunction);
doubleClick();
assertInFunction(callerFunction);
}
@Test
public void testXrefs_GroupByFunctionView_DoubleClickAddressInFunction() {
/*
XREF[20]: callerFunction4[2]: 00020bbc(c),
callerFunction5[5]: 00020fa4(c),
callerFunction6[3]: 0002138c(c),
00030004(c), 00030008(c),
0003000c(c), 00030010(c),
00030014(c), 00030018(c),
0003001c(c), 00030020(c),
00030024(c), 00030028(c)
*/
setGroupByFunctionOption(true);
goToXrefField(functionWithAllTypesOfCalls);
String callerFunction = "callerFunction5";
goToXrefField(functionWithAllTypesOfCalls, "00020fa4");
doubleClick();
assertInFunction(callerFunction);
}
@Test
public void testXrefs_DefaultView_DoubleClickAddressNotInFunction() {
/*
XREF[20]: callerFunction4:00020bbc(c),
callerFunction4:00020bc0(c),
callerFunction5:00020fa4(c),
callerFunction5:00020fa8(c),
callerFunction5:00020fac(c),
callerFunction5:00020fb0(c),
callerFunction5:00020fb4(c),
callerFunction6:0002138c(c),
callerFunction6:00021390(c),
callerFunction6:00021394(c),
00030004(c), 00030008(c),
0003000c(c), 00030010(c),
00030014(c), 00030018(c),
0003001c(c), 00030020(c),
00030024(c), 00030028(c)
*/
setGroupByFunctionOption(false);
goToXrefField(functionWithAllTypesOfCalls);
String addressNotInFunction = "00030018";
goToXrefField(functionWithAllTypesOfCalls, addressNotInFunction);
doubleClick();
assertAtAddress(addressNotInFunction);
}
@Test
public void testXrefs_GroupByFunctionView_DoubleClickAddressNotInFunction() {
/*
XREF[20]: callerFunction4[2]: 00020bbc(c),
callerFunction5[5]: 00020fa4(c),
callerFunction6[3]: 0002138c(c),
00030004(c), 00030008(c),
0003000c(c), 00030010(c),
00030014(c), 00030018(c),
0003001c(c), 00030020(c),
00030024(c), 00030028(c)
*/
setGroupByFunctionOption(true);
goToXrefField(functionWithAllTypesOfCalls);
String addressNotInFunction = "00030018";
goToXrefField(functionWithAllTypesOfCalls, addressNotInFunction);
doubleClick();
assertAtAddress(addressNotInFunction);
}
@Test
public void testXrefs_DefaultView_DoubleClickToShowAllXrefs() {
/*
XREF[20]: callerFunction4:00020bbc(c),
callerFunction4:00020bc0(c),
callerFunction5:00020fa4(c),
callerFunction5:00020fa8(c),
callerFunction5:00020fac(c),
callerFunction5:00020fb0(c),
callerFunction5:00020fb4(c),
callerFunction6:0002138c(c),
callerFunction6:00021390(c),
callerFunction6:00021394(c),
00030004(c), 00030008(c),
0003000c(c), 00030010(c),
00030014(c), 00030018(c),
0003001c(c), 00030020(c),
00030024(c), 00030028(c)
*/
setGroupByFunctionOption(false);
goToXrefHeaderField(functionWithAllTypesOfCalls);
doubleClick();
assertTableShowing();
}
@Test
public void testXrefs_GroupByFunctionView_DoubleClickToShowAllXrefs() {
/*
XREF[20]: callerFunction4[2]: 00020bbc(c),
callerFunction5[5]: 00020fa4(c),
callerFunction6[3]: 0002138c(c),
00030004(c), 00030008(c),
0003000c(c), 00030010(c),
00030014(c), 00030018(c),
0003001c(c), 00030020(c),
00030024(c), 00030028(c)
*/
setGroupByFunctionOption(true);
goToXrefHeaderField(functionWithAllTypesOfCalls);
doubleClick();
assertTableShowing();
}
@Test
public void testXrefs_DefaultView_DoubleClick_More_Text() {
/*
XREF[20]: callerFunction4:00020bbc(c),
callerFunction4:00020bc0(c),
callerFunction5:00020fa4(c),
callerFunction5:00020fa8(c),
callerFunction5:00020fac(c),
callerFunction5:00020fb0(c),
[more]
*/
setGroupByFunctionOption(false);
setMaxXrefs(5);
goToXrefField(functionWithAllTypesOfCalls, "more");
doubleClick();
assertTableShowing();
}
@Test
public void testXrefs_GroupByFunctionView_DoubleClick_More_Text() {
/*
XREF[20]: callerFunction4[2]: 00020bbc(c),
callerFunction5[4]: 00020fa4(c),
[more]
*/
setGroupByFunctionOption(true);
setMaxXrefs(5);
goToXrefField(functionWithAllTypesOfCalls, "more");
doubleClick();
assertTableShowing();
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void assertTableShowing() {
TableComponentProvider<?> table = waitForComponentProvider(TableComponentProvider.class);
GhidraProgramTableModel<?> model = table.getModel();
waitForCondition(() -> model.getRowCount() > 0);
}
private void assertAtAddress(String expected) {
Address actual = cb.getCurrentAddress();
assertEquals(expected, actual.toString());
}
private void assertInFunction(String text) {
Address addr = cb.getCurrentAddress();
FunctionManager functionManager = program.getFunctionManager();
Function function = functionManager.getFunctionContaining(addr);
assertEquals(text, function.getName());
}
private void doubleClick() {
click(cb, 2, true);
}
private void assertContainsRow(ListingTextField tf, String text) {
assertTrue("Expected '" + tf.getText() + "' to contain '" + text + "'",
tf.getText().contains(text));
}
private void goToXrefField(int addrOffset) {
assertTrue("Unable to navigate to xref field at " + Long.toHexString(addrOffset),
cb.goToField(addr(addrOffset), XRefFieldFactory.FIELD_NAME, 1, 1));
}
private void goToXrefHeaderField(int addrOffset) {
assertTrue("Unable to navigate to xref header field at " + Long.toHexString(addrOffset),
cb.goToField(addr(addrOffset), XRefHeaderFieldFactory.XREF_FIELD_NAME, 1, 1));
}
private void goToXrefField(int addrOffset, String text) {
// is there a better way to find a field when given an address and some text?
assertTrue("Unable to navigate to xref field at " + Long.toHexString(addrOffset),
cb.goToField(addr(addrOffset), XRefFieldFactory.FIELD_NAME, 1, 1));
ListingTextField tf = (ListingTextField) cb.getCurrentField();
int rows = tf.getNumRows();
for (int row = 0; row < rows; row++) {
String rowText = getRowText(tf, row);
int col = rowText.indexOf(text);
if (col >= 0) {
col++; // move past the start position to ensure we are inside of the field
assertTrue("Unable to navigate to xref field at " + Long.toHexString(addrOffset),
cb.goToField(addr(addrOffset), XRefFieldFactory.FIELD_NAME, row, col));
return;
}
}
fail("Uanble to find text at " + Long.toHexString(addrOffset) + "; text: '" + text + "'");
}
private String getRowText(ListingTextField tf, int row) {
List<FieldElement> rowElements = getRowElements(tf, row);
return StringUtils.join(rowElements);
}
private List<FieldElement> getRowElements(ListingTextField tf, int row) {
List<FieldElement> elements = new ArrayList<>();
int cols = tf.getNumCols(row);
for (int col = 0; col < cols; col++) {
FieldElement element = tf.getFieldElement(row, col);
if (!elements.contains(element)) {
elements.add(element);
}
}
return elements;
}
private boolean hasXrefField(int addrOffset) {
return cb.goToField(addr(addrOffset), XRefFieldFactory.FIELD_NAME, 1, 1);
}
private Function function(int addr) throws Exception {
return ensureFunction(addr);
}
private Function caller(int addr) throws Exception {
String name = "callerFunction" + (++callerCount);
return ensureFunction(addr, name);
}
private Function ensureFunction(long from) throws Exception {
ProgramDB p = builder.getProgram();
FunctionManager fm = p.getFunctionManager();
Function f = fm.getFunctionAt(addr(from));
if (f != null) {
return f;
}
String a = Long.toHexString(from);
return ensureFunction(from, "Function_" + a);
}
private Function ensureFunction(long from, String name) throws Exception {
ProgramDB p = builder.getProgram();
FunctionManager fm = p.getFunctionManager();
Function f = fm.getFunctionAt(addr(from));
if (f != null) {
return f;
}
String a = Long.toHexString(from);
return builder.createEmptyFunction(name, "0x" + a, 500, DataType.DEFAULT);
}
// creates n references from within caller to the given address
private void createCallerReference(int toAddr, Function caller, int n) {
int addr = (int) caller.getEntryPoint().getOffset();
createMemoryReferencesReference(toAddr, addr, n);
}
// create call reference to the given address
private void createNonFunctionReferences(int toAddr, int fromAddrRangeStart, int n) {
createMemoryReferencesReference(toAddr, fromAddrRangeStart, n);
}
private void createMemoryReferencesReference(int toAddr, int fromAddrRangeStart, int n) {
int offset = 4;
int addr = fromAddrRangeStart;
for (int i = 0; i < n; i++) {
addr += offset;
createReference(addr, toAddr);
}
}
private boolean createReference(long from, long to) {
ProgramDB p = builder.getProgram();
ReferenceManager rm = p.getReferenceManager();
Reference existing = rm.getReference(addr(from), addr(to), 0);
if (existing != null) {
return false;
}
builder.createMemoryCallReference("0x" + Long.toHexString(from),
"0x" + Long.toHexString(to));
return true;
}
private Address addr(long addr) {
return builder.addr(addr);
}
private void setGroupByFunctionOption(boolean b) {
setBooleanOption(XRefFieldFactory.GROUP_BY_FUNCTION_KEY, b);
}
private void setMaxXrefs(int n) {
setIntOptions(XRefFieldFactory.MAX_XREFS_KEY, n);
}
private void setBooleanOption(String name, boolean value) {
assertTrue("No such option '" + name + "'", fieldOptions.contains(name));
runSwing(() -> fieldOptions.setBoolean(name, value));
waitForSwing();
cb.updateNow();
}
private void setIntOptions(String name, int value) {
assertTrue("No such option '" + name + "'", fieldOptions.contains(name));
runSwing(() -> fieldOptions.setInt(name, value));
waitForSwing();
cb.updateNow();
}
}

View file

@ -33,7 +33,7 @@ public class ClangTextField extends WrappingVerticalLayoutTextField {
private FieldElement lineNumberFieldElement;
private static FieldElement createSingleLineElement(FieldElement[] textElements) {
return new CompositeFieldElement(textElements, 0, textElements.length);
return new CompositeFieldElement(textElements);
}
/**

View file

@ -48,7 +48,7 @@ public class DecompilerClipboardProvider extends ByteCopier
private static final PaintContext PAINT_CONTEXT = new PaintContext();
private static final ClipboardType TEXT_TYPE =
new ClipboardType(DataFlavor.stringFlavor, "Text");
private static final List<ClipboardType> COPY_TYPES = new LinkedList<ClipboardType>();
private static final List<ClipboardType> COPY_TYPES = new LinkedList<>();
static {
COPY_TYPES.add(TEXT_TYPE);
@ -58,7 +58,7 @@ public class DecompilerClipboardProvider extends ByteCopier
private FieldSelection selection;
private boolean copyFromSelectionEnabled;
private Set<ChangeListener> listeners = new CopyOnWriteArraySet<ChangeListener>();
private Set<ChangeListener> listeners = new CopyOnWriteArraySet<>();
private int spaceCharWidthInPixels = 7;
public DecompilerClipboardProvider(DecompilePlugin plugin, DecompilerProvider provider) {
@ -161,12 +161,12 @@ public class DecompilerClipboardProvider extends ByteCopier
return false;
}
protected Transferable copyText(TaskMonitor monitor) {
private Transferable copyText(TaskMonitor monitor) {
return createStringTransferable(getText());
}
String getText() {
StringBuffer buffer = new StringBuffer();
private String getText() {
StringBuilder buffer = new StringBuilder();
int numRanges = selection.getNumRanges();
for (int i = 0; i < numRanges; i++) {
appendText(buffer, selection.getFieldRange(i));
@ -174,7 +174,7 @@ public class DecompilerClipboardProvider extends ByteCopier
return buffer.toString();
}
void appendText(StringBuffer buffer, FieldRange fieldRange) {
private void appendText(StringBuilder buffer, FieldRange fieldRange) {
int startIndex = fieldRange.getStart().getIndex().intValue();
int endIndex = fieldRange.getEnd().getIndex().intValue();
if (startIndex == endIndex) { // single line selection (don't include padding)
@ -189,7 +189,7 @@ public class DecompilerClipboardProvider extends ByteCopier
}
}
private void appendText(StringBuffer buffer, int lineNumber,
private void appendText(StringBuilder buffer, int lineNumber,
FieldSelection singleLineSelection) {
if (singleLineSelection.isEmpty()) {
return;
@ -224,7 +224,7 @@ public class DecompilerClipboardProvider extends ByteCopier
}
}
private void appendTextSingleLine(StringBuffer buffer, int lineNumber,
private void appendTextSingleLine(StringBuilder buffer, int lineNumber,
FieldSelection singleLineSelection) {
if (singleLineSelection.isEmpty()) {
return;

View file

@ -83,7 +83,7 @@ abstract public class AbstractTextFieldElement implements FieldElement {
@Override
public int getMaxCharactersForWidth(int width) {
return attributedString.getColumnPosition(width);
return attributedString.getCharPosition(width);
}
@Override
@ -112,7 +112,8 @@ abstract public class AbstractTextFieldElement implements FieldElement {
@Override
public RowColLocation getDataLocationForCharacterIndex(int characterIndex) {
if (characterIndex < 0 || characterIndex > attributedString.getText().length()) {
throw new IllegalArgumentException("columnPosition is out of range: " + characterIndex);
throw new IllegalArgumentException("columnPosition is out of range: " + characterIndex +
"; range is [0," + attributedString.getText().length() + "]");
}
return new RowColLocation(row, column + characterIndex);
}

View file

@ -136,11 +136,11 @@ public class AttributedString {
return fontMetrics.getMaxDescent() + UNDERLINE_HEIGHT;
}
public int getColumnPosition(int width) {
public int getCharPosition(int x) {
int subWidth = getIconWidth();
for (int i = 0; i < text.length(); i++) {
subWidth += fontMetrics.charWidth(text.charAt(i));
if (subWidth > width) {
if (subWidth > x) {
return i;
}
}

View file

@ -106,8 +106,7 @@ public class ClippingTextField implements TextField {
@Override
public int getCol(int row, int x) {
int xPos = Math.max(x - startX, 0); // make x relative to this fields
// coordinate system.
int xPos = Math.max(x - startX, 0); // make x relative to this fields coordinate system
return textElement.getMaxCharactersForWidth(xPos);
}
@ -134,7 +133,13 @@ public class ClippingTextField implements TextField {
}
private int getNumCols() {
return textElement.length() + 1; // allow one column past the end of the text
// allow one column past the end of the text to allow the cursor to be placed after the text
return textElement.length() + 1;
}
@Override
public int getNumDataRows() {
return 1;
}
@Override
@ -217,7 +222,8 @@ public class ClippingTextField implements TextField {
@Override
public void paint(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager colorManager, RowColLocation cursorLoc, int rowHeight) {
Rectangle clip, FieldBackgroundColorManager colorManager, RowColLocation cursorLoc,
int rowHeight) {
if (context.isPrinting()) {
print(g, context);
}
@ -329,14 +335,21 @@ public class ClippingTextField implements TextField {
*/
@Override
public RowColLocation screenToDataLocation(int screenRow, int screenColumn) {
return textElement.getDataLocationForCharacterIndex(screenColumn);
return originalElement.getDataLocationForCharacterIndex(screenColumn);
}
@Override
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) {
int column = textElement.getCharacterIndexForDataLocation(dataRow, dataColumn);
return new RowColLocation(0, Math.max(column, 0));
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 RowColLocation(0, column);
}
private int findX(int col) {
@ -381,7 +394,8 @@ public class ClippingTextField implements TextField {
@Override
public RowColLocation textOffsetToScreenLocation(int textOffset) {
return new RowColLocation(0, Math.min(textOffset, textElement.getText().length() - 1));
// allow the max position to be just after the last character
return new RowColLocation(0, Math.min(textOffset, textElement.getText().length()));
}
@Override
@ -395,9 +409,6 @@ public class ClippingTextField implements TextField {
@Override
public FieldElement getFieldElement(int screenRow, int screenColumn) {
// TODO - this used to return the clipped value, which is not our clients wanted (at least one). If
// any odd navigation/tracking/action issues appear, then this could be the culprit.
// return textElement.getFieldElement(screenColumn);
return originalElement.getFieldElement(screenColumn);
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -27,7 +26,7 @@ import javax.swing.JComponent;
public class CompositeAttributedString extends AttributedString {
private String fullText;
private AttributedString[] attributedStrings;
protected AttributedString[] attributedStrings;
private int heightAbove = -1;
private int heightBelow = -1;
@ -35,7 +34,7 @@ public class CompositeAttributedString extends AttributedString {
this(stringList.toArray(new AttributedString[stringList.size()]));
}
public CompositeAttributedString(AttributedString[] attributedStrings) {
public CompositeAttributedString(AttributedString... attributedStrings) {
this.attributedStrings = attributedStrings;
}
@ -54,17 +53,17 @@ public class CompositeAttributedString extends AttributedString {
}
@Override
public int getColumnPosition(int width) {
int remainingWidth = width;
public int getCharPosition(int x) {
int remainingWidth = x;
int totalCharacters = 0;
for (int i = 0; i < attributedStrings.length; i++) {
int nextWidth = attributedStrings[i].getStringWidth();
for (AttributedString attributedString : attributedStrings) {
int nextWidth = attributedString.getStringWidth();
if (nextWidth >= remainingWidth) {
totalCharacters += attributedStrings[i].getColumnPosition(remainingWidth);
totalCharacters += attributedString.getCharPosition(remainingWidth);
break;
}
remainingWidth -= nextWidth;
totalCharacters += attributedStrings[i].length();
totalCharacters += attributedString.length();
}
return totalCharacters;
@ -86,8 +85,8 @@ public class CompositeAttributedString extends AttributedString {
public int getHeightAbove() {
if (heightAbove < 0) {
heightAbove = 0;
for (int i = 0; i < attributedStrings.length; i++) {
heightAbove = Math.max(heightAbove, attributedStrings[i].getHeightAbove());
for (AttributedString attributedString : attributedStrings) {
heightAbove = Math.max(heightAbove, attributedString.getHeightAbove());
}
}
return heightAbove;
@ -97,23 +96,23 @@ public class CompositeAttributedString extends AttributedString {
public int getHeightBelow() {
if (heightBelow < 0) {
heightBelow = 0;
for (int i = 0; i < attributedStrings.length; i++) {
heightBelow = Math.max(heightBelow, attributedStrings[i].getHeightBelow());
for (AttributedString attributedString : attributedStrings) {
heightBelow = Math.max(heightBelow, attributedString.getHeightBelow());
}
}
return heightBelow;
}
// =============================================================================================
// font metrics methods
// =============================================================================================
// =============================================================================================
// font metrics methods
// =============================================================================================
@Override
public int getStringWidth() {
if (textWidth == -1) {
textWidth = 0;
for (int i = 0; i < attributedStrings.length; i++) {
textWidth += attributedStrings[i].getStringWidth();
for (AttributedString attributedString : attributedStrings) {
textWidth += attributedString.getStringWidth();
}
}
return textWidth;
@ -123,24 +122,24 @@ public class CompositeAttributedString extends AttributedString {
public String getText() {
if (fullText == null) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < attributedStrings.length; i++) {
buffer.append(attributedStrings[i].getText());
for (AttributedString attributedString : attributedStrings) {
buffer.append(attributedString.getText());
}
fullText = buffer.toString();
}
return fullText;
}
// =============================================================================================
// paint methods
// =============================================================================================
// =============================================================================================
// paint methods
// =============================================================================================
@Override
public void paint(JComponent c, Graphics g, int x, int y) {
int xPos = x;
for (int i = 0; i < attributedStrings.length; i++) {
attributedStrings[i].paint(c, g, xPos, y);
xPos += attributedStrings[i].getStringWidth();
for (AttributedString attributedString : attributedStrings) {
attributedString.paint(c, g, xPos, y);
xPos += attributedString.getStringWidth();
}
}

View file

@ -24,7 +24,7 @@ import javax.swing.JComponent;
import docking.widgets.fieldpanel.support.RowColLocation;
/**
* A FieldElement that is composed of other FieldElements.
* A FieldElement that is composed of other FieldElements. The elements are laid out horizontally.
*/
public class CompositeFieldElement implements FieldElement {
@ -34,19 +34,14 @@ public class CompositeFieldElement implements FieldElement {
private int textWidth = -1;
private String fullText;
public CompositeFieldElement(List<? extends FieldElement> stringList) {
this(stringList.toArray(new FieldElement[stringList.size()]));
public CompositeFieldElement(List<? extends FieldElement> elements) {
this(elements.toArray(new FieldElement[elements.size()]));
}
public CompositeFieldElement(FieldElement[] fieldElements) {
this.fieldElements = fieldElements;
}
public CompositeFieldElement(FieldElement[] elements, int start, int length) {
fieldElements = new FieldElement[length];
System.arraycopy(elements, start, fieldElements, 0, length);
}
private IndexedOffset getIndexedOffsetForCharPosition(int charPosition) {
int n = 0;
for (int i = 0; i < fieldElements.length; i++) {
@ -130,7 +125,7 @@ public class CompositeFieldElement implements FieldElement {
@Override
public String getText() {
if (fullText == null) {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
for (FieldElement fieldElement : fieldElements) {
buffer.append(fieldElement.getText());
}
@ -217,6 +212,11 @@ public class CompositeFieldElement implements FieldElement {
return getText().length();
}
@Override
public String toString() {
return getText();
}
//==================================================================================================
// Location Info
//==================================================================================================
@ -229,12 +229,14 @@ public class CompositeFieldElement implements FieldElement {
@Override
public int getCharacterIndexForDataLocation(int dataRow, int dataColumn) {
int columnCount = 0;
int columnsSoFar = 0;
for (int i = fieldElements.length - 1; i >= 0; i--) {
columnCount += fieldElements[i].length();
columnsSoFar += fieldElements[i].length();
int column = fieldElements[i].getCharacterIndexForDataLocation(dataRow, dataColumn);
if (column != -1) {
return length() - columnCount + column;
// column value is relative to the current field; convert it to this field's offset
int fieldStart = length() - columnsSoFar;
return fieldStart + column;
}
}

View file

@ -0,0 +1,596 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.fieldpanel.field;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.JComponent;
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;
/**
* A {@link TextField} that takes in other TextFields.
*
* <P>This class allows clients to create custom text layout behavior by combining individual
* TextFields that dictate layout behavior. As an example, consider this rendering:
* <pre>
* 1) This is some text...
* 2) This
* is
* more
* text
* </pre>
* In this example, 1) is a row of text inside of a {@link ClippingTextField}. Row 2) is a
* multi-line text rendering specified in a single {@link FlowLayoutTextField}, using a
* narrow width to trigger the field to place each element on its own line.
*/
public class CompositeVerticalLayoutTextField implements TextField {
// the view rows, which may be a clipped version of the client fields
private List<FieldRow> fieldRows;
private int startX;
private int width;
private int preferredWidth;
private HighlightFactory hlFactory;
private int height;
private int heightAbove;
private int numRows;
private int numDataRows;
private boolean isPrimary;
private String fullText;
// all text, including any clipped text; lines.size() == fields.size()
private List<String> lines;
// used in the getText() method to separate rows without adding newlines
private String rowSeparator;
private boolean isClipped;
public CompositeVerticalLayoutTextField(List<TextField> fields, int startX, int width,
int maxLines, HighlightFactory hlFactory) {
this(fields, startX, width, maxLines, hlFactory, " ");
}
protected CompositeVerticalLayoutTextField(List<TextField> fields, int startX, int width,
int maxLines, HighlightFactory hlFactory, String rowSeparator) {
this.startX = startX;
this.width = width;
this.hlFactory = hlFactory;
this.rowSeparator = rowSeparator;
lines = generateLines(fields);
fullText = generateText(fields, rowSeparator);
heightAbove = (fields.get(0)).getHeightAbove();
fieldRows = layoutRows(fields, maxLines);
calculateRows(fields);
calculatePreferredWidth();
calculateHeight();
}
private List<String> generateLines(List<TextField> fields) {
List<String> list = new ArrayList<>();
for (TextField field : fields) {
list.add(field.getTextWithLineSeparators());
}
return list;
}
private String generateText(List<TextField> fields, String delimiter) {
StringBuilder buf = new StringBuilder();
for (TextField element : fields) {
buf.append(element.getText()).append(delimiter);
}
return buf.toString();
}
private List<FieldRow> layoutRows(List<TextField> fields, int maxLines) {
List<FieldRow> newSubFields = new ArrayList<>();
int heightSoFar = -heightAbove;
int currentRow = 0;
boolean tooManyLines = fields.size() > maxLines;
for (int i = 0; i < fields.size() && i < maxLines; i++) {
TextField field = fields.get(i);
if (tooManyLines && (i == maxLines - 1)) {
FieldElement element = field.getFieldElement(0, 0);
TextField newField = createClippedField(element);
newSubFields.add(new FieldRow(newField, currentRow, heightSoFar));
isClipped = true;
}
else {
newSubFields.add(new FieldRow(field, currentRow, heightSoFar));
isClipped |= field.isClipped();
}
heightSoFar += field.getHeight();
currentRow += field.getNumRows();
}
isClipped |= tooManyLines;
return newSubFields;
}
private ClippingTextField createClippedField(FieldElement element) {
FieldElement[] elements = new FieldElement[] {
element,
new StrutFieldElement(500)
};
FieldElement compositeElement = new CompositeFieldElement(elements);
return new ClippingTextField(startX, width, compositeElement, hlFactory);
}
private void calculateHeight() {
for (FieldRow row : fieldRows) {
height += row.field.getHeight();
}
}
private void calculatePreferredWidth() {
preferredWidth = 0;
for (FieldRow row : fieldRows) {
preferredWidth = Math.max(preferredWidth, row.field.getPreferredWidth());
}
}
private void calculateRows(List<TextField> fields) {
numRows = 0;
for (FieldRow row : fieldRows) {
numRows += row.field.getNumRows();
}
numDataRows = 0;
for (TextField field : fields) {
numDataRows += field.getNumDataRows();
}
}
@Override
public String toString() {
return getText();
}
@Override
public int getWidth() {
return width;
}
@Override
public int getPreferredWidth() {
return preferredWidth;
}
@Override
public int getHeight() {
return height;
}
@Override
public int getStartX() {
return startX;
}
@Override
public int getNumDataRows() {
return numDataRows;
}
@Override
public int getNumRows() {
return numRows;
}
@Override
public int getHeightAbove() {
return heightAbove;
}
@Override
public int getHeightBelow() {
return height - heightAbove;
}
@Override
public boolean isPrimary() {
return isPrimary;
}
@Override
public void rowHeightChanged(int newHeightAbove, int newHeightBelow) {
// don't care
}
@Override
public boolean isClipped() {
return isClipped;
}
@Override
public void setPrimary(boolean state) {
isPrimary = state;
}
@Override
public String getText() {
return fullText;
}
@Override
public String getTextWithLineSeparators() {
return StringUtils.join(lines, '\n');
}
@Override
public void paint(JComponent c, Graphics g, PaintContext context, Rectangle clip,
FieldBackgroundColorManager colorManager, RowColLocation cursorLocation,
int rowHeight) {
// the graphics have been translated such that the first line of text's base line is
// at y=0 (So if we are not clipped, we will drawing from a negative value that is the
// font's height above the baseline (-heightAbove) to rowHeight (-heightAbove)
int myStartY = -heightAbove;
int myEndY = myStartY + rowHeight;
int clipStartY = clip.y;
int clipEndY = clip.y + clip.height;
Color fieldBackgroundColor = colorManager.getBackgroundColor();
if (fieldBackgroundColor != null) {
g.setColor(fieldBackgroundColor);
// restrict background rectangle to clipping rectangle
int startY = Math.max(myStartY, clipStartY);
int endY = Math.min(myEndY, clipEndY);
int clippedHeight = endY - startY;
g.fillRect(startX, startY, width, clippedHeight);
}
FieldRow cursorRow = null;
if (cursorLocation != null) {
cursorRow = getFieldRow(cursorLocation.row());
}
int startY = myStartY;
int translatedY = 0;
for (int i = 0; i < fieldRows.size(); i++) {
// if past clipping region we are done
if (startY > clipEndY) {
break;
}
FieldRow fieldRow = fieldRows.get(i);
TextField field = fieldRow.field;
int subFieldHeight = fieldRow.field.getHeight();
int endY = startY + subFieldHeight;
// if any part of the line is in the clip region, draw it
if (endY >= clipStartY) {
RowColLocation cursor = null;
if (fieldRow == cursorRow) {
int relativeRow = fieldRow.getRelativeRow(cursorLocation.row());
cursor = cursorLocation.withRow(relativeRow);
}
field.paint(c, g, context, clip, colorManager, cursor, rowHeight);
}
// translate for next row of text
startY += subFieldHeight;
g.translate(0, subFieldHeight);
translatedY += subFieldHeight;
}
// restore the graphics to where it was when we started.
g.translate(0, -translatedY);
}
@Override
public boolean contains(int x, int y) {
if ((x >= startX) && (x < startX + width) && (y >= -heightAbove) &&
(y < height - heightAbove)) {
return true;
}
return false;
}
public String getRowSeparator() {
return rowSeparator;
}
private FieldRow getFieldRow(int screenRow) {
int currentRow = 0;
for (FieldRow row : fieldRows) {
int n = row.field.getNumRows();
if (currentRow + n > screenRow) {
return row;
}
currentRow += n;
}
return fieldRows.get(fieldRows.size() - 1);
}
private FieldRow getFieldRowFromDataRow(int dataRow) {
int currentRow = 0;
for (FieldRow row : fieldRows) {
if (currentRow >= dataRow) {
return row;
}
currentRow += row.field.getNumDataRows();
}
return fieldRows.get(fieldRows.size() - 1);
}
// get all rows from 0 to max inclusive
private List<FieldRow> getAllRows(int maxRow) {
int currentRow = 0;
List<FieldRow> list = new ArrayList<>();
for (FieldRow row : fieldRows) {
if (currentRow > maxRow) {
break;
}
list.add(row);
currentRow += row.field.getNumRows();
}
return list;
}
// for testing
protected List<TextField> getAllRowsUpTo(int maxRowInclusive) {
return getAllRows(maxRowInclusive)
.stream()
.map(fieldRow -> fieldRow.field)
.collect(Collectors.toList());
}
@Override
public FieldElement getFieldElement(int screenRow, int screenColumn) {
FieldRow fieldRow = getFieldRow(screenRow);
int relativeRow = fieldRow.getRelativeRow(screenRow);
return fieldRow.field.getFieldElement(relativeRow, screenColumn);
}
@Override
public int getNumCols(int row) {
FieldRow fieldRow = getFieldRow(row);
int relativeRow = fieldRow.getRelativeRow(row);
return fieldRow.field.getNumCols(relativeRow);
}
@Override
public int getX(int row, int col) {
FieldRow fieldRow = getFieldRow(row);
int relativeRow = fieldRow.getRelativeRow(row);
return fieldRow.field.getX(relativeRow, col);
}
@Override
public int getY(int row) {
int y = -heightAbove;
List<FieldRow> rows = getAllRows(row);
for (FieldRow fieldRow : rows) {
y += fieldRow.field.getHeight();
}
return y;
}
@Override
public int getRow(int y) {
if (y < 0) {
return 0;
}
int heightSoFar = 0;
for (FieldRow fieldRow : fieldRows) {
int fieldHeight = fieldRow.field.getHeight();
int bottom = fieldHeight + heightSoFar;
if (bottom > y) {
int relativeY = y - heightSoFar;
int relativeRow = fieldRow.field.getRow(relativeY);
int displayRow = fieldRow.fromRelativeRow(relativeRow);
return displayRow;
}
heightSoFar += fieldHeight;
}
return getNumRows() - 1;
}
@Override
public int getCol(int row, int x) {
FieldRow fieldRow = getFieldRow(row);
int relativeRow = fieldRow.getRelativeRow(row);
return fieldRow.field.getCol(relativeRow, x);
}
@Override
public boolean isValid(int row, int col) {
if ((row < 0) || (row >= getNumRows())) {
return false;
}
FieldRow fieldRow = getFieldRow(row);
int relativeRow = fieldRow.getRelativeRow(row);
return fieldRow.field.isValid(relativeRow, col);
}
@Override
public Rectangle getCursorBounds(int row, int col) {
if ((row < 0) || (row >= getNumRows())) {
return null;
}
List<FieldRow> rows = getAllRows(row);
FieldRow cursorRow = rows.get(rows.size() - 1);
int relativeRow = cursorRow.getRelativeRow(row);
Rectangle r = cursorRow.field.getCursorBounds(relativeRow, col);
for (int i = 0; i < rows.size() - 1; i++) {
FieldRow previousRow = rows.get(i);
r.y += previousRow.field.getHeight();
}
return r;
}
@Override
public int getScrollableUnitIncrement(int topOfScreen, int direction, int max) {
if ((topOfScreen < -heightAbove) || (topOfScreen > height - heightAbove)) {
return max;
}
int row = getRow(topOfScreen);
int y = getY(row);
int rowOffset = topOfScreen - y;
FieldRow fieldRow = getFieldRow(row);
int rowHeight = fieldRow.field.getHeight();
if (direction > 0) { // if scrolling down
return rowHeight - rowOffset;
}
else if (rowOffset == 0) {
return -rowHeight;
}
else {
return -rowOffset;
}
}
@Override
public RowColLocation screenToDataLocation(int screenRow, int screenColumn) {
screenRow = Math.min(screenRow, numRows - 1);
screenRow = Math.max(screenRow, 0);
FieldRow fieldRow = getFieldRow(screenRow);
screenColumn = Math.min(screenColumn, fieldRow.field.getText().length());
screenColumn = Math.max(screenColumn, 0);
int relativeRow = fieldRow.getRelativeRow(screenRow);
return fieldRow.field.screenToDataLocation(relativeRow, screenColumn);
}
@Override
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) {
FieldRow fieldRow = getFieldRowFromDataRow(dataRow);
RowColLocation location = fieldRow.field.dataToScreenLocation(dataRow, dataColumn);
int relativeRow = fieldRow.fromRelativeRow(location.row());
return location.withRow(relativeRow);
}
@Override
public int screenLocationToTextOffset(int row, int col) {
if (row >= numRows) {
return getText().length();
}
int extraSpace = rowSeparator.length();
int len = 0;
List<FieldRow> rows = getAllRows(row);
int n = rows.size() - 1;
for (int i = 0; i < n; i++) {
FieldRow fieldRow = rows.get(i);
len += fieldRow.field.getText().length() + extraSpace;
}
FieldRow lastRow = rows.get(n);
int relativeRow = lastRow.getRelativeRow(row);
len += lastRow.field.screenLocationToTextOffset(relativeRow, col);
return len;
}
@Override
public RowColLocation textOffsetToScreenLocation(int textOffset) {
int extraSpace = rowSeparator.length();
int n = fieldRows.size();
int textOffsetSoFar = 0;
for (int i = 0; i < n; i++) {
if (textOffsetSoFar > textOffset) {
break;
}
FieldRow fieldRow = fieldRows.get(i);
int length = fieldRow.field.getText().length() + extraSpace;
int end = textOffsetSoFar + length;
if (end > textOffset) {
int relativeOffset = textOffset - textOffsetSoFar;
RowColLocation location = fieldRow.field.textOffsetToScreenLocation(relativeOffset);
int screenRow = fieldRow.fromRelativeRow(location.row());
return location.withRow(screenRow);
}
textOffsetSoFar += length;
}
FieldRow lastRow = fieldRows.get(fieldRows.size() - 1);
int length = lastRow.field.getText().length();
return new DefaultRowColLocation(numRows - 1, length);
}
private class FieldRow {
private TextField field;
private int displayRowOffset;
private int yOffset;
FieldRow(TextField field, int rowOffset, int yOffset) {
this.field = field;
this.displayRowOffset = rowOffset;
}
// used to turn given row into 0 for this composite field
int getRelativeRow(int displayRow) {
return displayRow - displayRowOffset;
}
int fromRelativeRow(int relativeRow) {
return relativeRow + displayRowOffset;
}
int getY() {
return yOffset;
}
@Override
public String toString() {
return Json.toString(this);
}
}
}

View file

@ -21,6 +21,7 @@ import javax.swing.JComponent;
import docking.widgets.fieldpanel.internal.FieldBackgroundColorManager;
import docking.widgets.fieldpanel.internal.PaintContext;
import docking.widgets.fieldpanel.support.DefaultRowColLocation;
import docking.widgets.fieldpanel.support.RowColLocation;
/**
@ -79,6 +80,11 @@ public class EmptyTextField implements Field {
return startX;
}
@Override
public int getNumDataRows() {
return 1;
}
@Override
public int getNumRows() {
return 1;
@ -123,7 +129,8 @@ public class EmptyTextField implements Field {
@Override
public void paint(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc, int rowHeight) {
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc,
int rowHeight) {
paintCursor(g, context.getCursorColor(), cursorLoc);
}
@ -227,7 +234,7 @@ public class EmptyTextField implements Field {
@Override
public RowColLocation textOffsetToScreenLocation(int textOffset) {
return new RowColLocation(0, 0);
return new DefaultRowColLocation();
}
@Override

View file

@ -30,155 +30,180 @@ import docking.widgets.fieldpanel.support.RowColLocation;
public interface Field {
/**
* Returns the current width of this field.
* Returns the current width of this field
* @return the current width of this field
*/
int getWidth();
public int getWidth();
/**
* The minimum required width to paint the contents of this field
* @return the minimum required width to paint the contents of this field
*/
int getPreferredWidth();
public int getPreferredWidth();
/**
* Returns the height of this field when populated with the given data.
* Returns the height of this field when populated with the given data
* @return the height
*/
int getHeight();
public int getHeight();
/**
* Returns the height above the baseLine.
* Returns the height above the baseLine
* @return the height above
*/
int getHeightAbove();
public int getHeightAbove();
/**
* Returns the height below the baseLine.
* Returns the height below the baseLine
* @return the height below
*/
int getHeightBelow();
public int getHeightBelow();
/**
* Returns the horizontal position of this field.
* Returns the horizontal position of this field
* @return the position
*/
int getStartX();
public int getStartX();
/**
* Paints this field.
* Paints this field
* @param c the component to paint onto
* @param g the graphics context.
* @param g the graphics context
* @param context common paint parameters
* @param clip the clipping region to paint into
* @param colorManager contains background color information for the field.
* @param colorManager contains background color information for the field
* @param cursorLoc the row,column cursor location within the field or null if the field does
* not contain the cursor
* @param rowHeight the number of pixels in each row of text in the field.
* @param rowHeight the number of pixels in each row of text in the field
*/
void paint(JComponent c, Graphics g, PaintContext context, Rectangle clip,
public void paint(JComponent c, Graphics g, PaintContext context, Rectangle clip,
FieldBackgroundColorManager colorManager, RowColLocation cursorLoc, int rowHeight);
/**
* Returns true if the given point is in this field.
* @param x the horizontal coordinate of the point.
* @param y the relatve y position in this layout
* Returns true if the given point is in this field
* @param x the horizontal coordinate of the point
* @param y the relative y position in this layout
* @return true if the given point is in this field
*/
boolean contains(int x, int y);
public boolean contains(int x, int y);
/**
* Returns the number of data model rows represented by this field. Some fields may change
* the row count by wrapping or truncating. The value returned here will be the original data
* row count before any transformations were applied.
* @return the number of data rows
*/
public int getNumDataRows();
/**
* Returns the number of rows in this field
* @return the number of rows in this field
*/
int getNumRows();
public int getNumRows();
/**
* Returns the number of columns in the given row.
* @param row the row from which to get the number of columns.
* Returns the number of columns in the given row
* @param row the row from which to get the number of columns; this is the screen row
* @return the number of columns
*/
int getNumCols(int row);
public int getNumCols(int row);
/**
* Returns the x coordinate for the given cursor position.
* @param row the text row of interest.
* @param col the character column.
* Returns the x coordinate for the given cursor position
* @param row the text row of interest
* @param col the character column
* @return the x value
*/
int getX(int row, int col);
public int getX(int row, int col);
/**
* Returns the y coordinate for the given row.
* @param row the text row of interest.
* Returns the y coordinate for the given row
* @param row the text row of interest
* @return the y value
*/
int getY(int row);
public int getY(int row);
/**
* Returns the row containing the given y coordinate.
* @param y vertical pixel coordinate relative to the top of the screen.
* Returns the row containing the given y coordinate
* @param y vertical pixel coordinate relative to the top of the screen
* @return the row
*/
int getRow(int y);
public int getRow(int y);
/**
* Returns the cursor column position for the given x coordinate on the given
* row.
* @param row the text row to find the column on.
* @param x the horizontal pixel coordinate for which to find the character position.
* Returns the cursor column position for the given x coordinate on the given row
* @param row the text row to find the column on
* @param x the horizontal pixel coordinate for which to find the character position
* @return the column
*/
int getCol(int row, int x);
public int getCol(int row, int x);
/**
* Returns true if the given row and column represent a valid location for
* this field with the given data;
* @param row the text row.
* @param col the character position.
* Returns true if the given row and column represent a valid location for this field with
* the given data
* @param row the text row
* @param col the character position
* @return tru if valid
*/
boolean isValid(int row, int col);
public boolean isValid(int row, int col);
/**
* Returns a bounding rectangle for the cursor at the given position.
* @param row the text row.
* @param col the character postion.
* Returns a bounding rectangle for the cursor at the given position
* @param row the text row
* @param col the character position
* @return the rectangle
*/
Rectangle getCursorBounds(int row, int col);
public Rectangle getCursorBounds(int row, int col);
/**
* Returns the amount to scroll to the next or previous line
* @param topOfScreen - the current y pos of the top of the screen.
* @param direction - the direction of the scroll (1 down, -1 up)
* @param max - the maximum amount to scroll for the entire row - will
* be positive for down, and negative for up)
* @param topOfScreen the current y position of the top of the screen
* @param direction the direction of the scroll (1 down, -1 up)
* @param max the maximum amount to scroll for the entire row - will be positive for down, and
* negative for up)
* @return the scroll amount
*/
int getScrollableUnitIncrement(int topOfScreen, int direction, int max);
public int getScrollableUnitIncrement(int topOfScreen, int direction, int max);
/**
* Returns true if this field is "primary" (the most important)
* field; used to determine the "primary" line in the layout.
* Returns true if this field is "primary" (the most important) field; used to determine the
* "primary" line in the layout
* @return true if this field is "primary"
*/
boolean isPrimary();
public boolean isPrimary();
/**
* notifies field that the rowHeight changed
* @param heightAbove the height above the baseline
* @param heightBelow the height below the baseline.
* @param heightBelow the height below the baseline
*/
void rowHeightChanged(int heightAbove, int heightBelow);
public void rowHeightChanged(int heightAbove, int heightBelow);
/**
* Returns a string containing all the text in the field.
* Returns a string containing all the text in the field
* @return the string
*/
String getText();
public String getText();
/**
* Returns a string containing all the text in the field with extra linefeeds
* @return
* Returns a string containing all the text in the field with extra newlines
* @return a string containing all the text in the field with extra newlines
*/
String getTextWithLineSeparators();
public String getTextWithLineSeparators();
/**
* Returns the row, column position for an offset into the string returned by getText().
* @param textOffset the offset into the entire text string for this field.
* Returns the row, column position for an offset into the string returned by getText()
* @param textOffset the offset into the entire text string for this field
* @return a RowColLocation that contains the row,column location in the field for a position in
* the overall field text.
* the overall field text
*/
RowColLocation textOffsetToScreenLocation(int textOffset);
public RowColLocation textOffsetToScreenLocation(int textOffset);
/**
* Returns the text offset in the overall field text string for the given row and column.
* @param row the row.
* @param col the column.
* Returns the text offset in the overall field text string for the given row and column
* @param row the row
* @param col the column
* @return the offset
*/
int screenLocationToTextOffset(int row, int col);
public int screenLocationToTextOffset(int row, int col);
}

View file

@ -129,13 +129,15 @@ public interface FieldElement {
* Returns the character index appropriate for the given data location
* @param dataRow the row in the data model as determined by the creating field factory.
* @param dataColumn the column in the data model as determined by the creating field factory.
* @return the character index appropriate for the given data location
* @return the character index appropriate for the given data location; -1 if this field does
* not contain the given location
*/
public int getCharacterIndexForDataLocation(int dataRow, int dataColumn);
/**
* Paints the text contained in this field element at the given x,y screen coordinate using the
* given Graphics object.
* @param c the component being painted.
* @param g the Graphics object used to paint the field text.
* @param x the horizontal screen position to paint
* @param y the vertical screen position to paint.
@ -144,7 +146,7 @@ public interface FieldElement {
/**
* Returns the inner-most FieldElement inside this field element at the given location
* @param column the charactor offset.
* @param column the character offset.
* @return the inner-most FieldElement inside this field element at the given location
*/
public FieldElement getFieldElement(int column);

View file

@ -15,67 +15,82 @@
*/
package docking.widgets.fieldpanel.field;
import java.util.ArrayList;
import java.util.List;
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 AttributedStrings and places
* as many that will fit on a line without clipping before continuing to the next line.
*/
public class FlowLayoutTextField extends VerticalLayoutTextField {
/**
* This constructor will create a text field that will render one line of
* text. If <code>metrics.stringWidth(text) &gt; width</code>, then the text
* will be clipped. No wrapping will be performed. If <code>text</code>
* contains the highlight string, then it will be highlighted using the
* This constructor will create a text field that will render one line of text. If
* <code>metrics.stringWidth(text) &gt; width</code>, then the text will be wrapped.
* If <code>text</code> contains the highlight string, then it will be highlighted using the
* highlight color.
*
* @param textElements
* the AttributedStrings to display
* @param startX
* the x position to draw the string
* @param width
* the max width allocated to this field
* @param maxLines
* the max number of lines to display
* @param hlFactory
* the highlight factory
* @param textElements the AttributedStrings to display
* @param startX the x position to draw the string
* @param width the max width allocated to this field
* @param maxLines the max number of lines to display
* @param hlFactory the highlight factory
* @deprecated use the constructor that takes a list
*/
@Deprecated(since = "10.1", forRemoval = true)
public FlowLayoutTextField(FieldElement[] textElements, int startX,
int width, int maxLines, HighlightFactory hlFactory) {
super(createLineElements(textElements, width), startX, width, maxLines, hlFactory,"");
this(Arrays.asList(textElements), startX, width, maxLines, hlFactory);
}
private static FieldElement[] createLineElements(FieldElement[] textElements, int width) {
List<FieldElement> subFields = new ArrayList<FieldElement>();
/**
* This constructor will create a text field that will render one line of text. If
* <code>metrics.stringWidth(text) &gt; width</code>, then the text will be wrapped.
* If <code>text</code> contains the highlight string, then it will be highlighted using the
* highlight color.
*
* @param elements the AttributedStrings to display
* @param startX the x position to draw the string
* @param width the max width allocated to this field
* @param maxLines the max number of lines to display
* @param hlFactory the highlight factory
*/
public FlowLayoutTextField(List<FieldElement> elements, int startX,
int width, int maxLines, HighlightFactory hlFactory) {
super(createLineElements(elements, width), startX, width, maxLines, hlFactory, "");
}
private static List<FieldElement> createLineElements(List<FieldElement> elements,
int width) {
List<FieldElement> subFields = new ArrayList<>();
int currentIndex = 0;
while (currentIndex < textElements.length) {
int numberPerLine = getNumberOfElementsPerLine(textElements, currentIndex, width);
subFields.add(new CompositeFieldElement(textElements, currentIndex, numberPerLine));
while (currentIndex < elements.size()) {
int numberPerLine = getNumberOfElementsPerLine(elements, currentIndex, width);
subFields.add(createLine(elements, currentIndex, numberPerLine));
currentIndex += numberPerLine;
}
return subFields.toArray(new FieldElement[subFields.size()]);
return subFields;
}
private static int getNumberOfElementsPerLine(FieldElement[] elements, int start, int width) {
private static CompositeFieldElement createLine(List<FieldElement> elements, int from,
int length) {
return new CompositeFieldElement(elements.subList(from, from + length));
}
private static int getNumberOfElementsPerLine(List<FieldElement> elements, int start,
int width) {
int currentWidth = 0;
int count = 0;
int n = elements.length;
for (int i = start; i < n; i++) {
currentWidth += elements[i].getStringWidth();
for (FieldElement element : elements) {
currentWidth += element.getStringWidth();
count++;
if (currentWidth > width) {
return Math.max(count - 1, 1);
}
}
return elements.length - start;
return elements.size() - start;
}
}

View file

@ -150,6 +150,11 @@ public class ReverseClippingTextField implements TextField {
return textElement.length() + 1; // allow one column past the end of the text
}
@Override
public int getNumDataRows() {
return 1;
}
@Override
public int getNumRows() {
return 1;
@ -230,7 +235,8 @@ public class ReverseClippingTextField implements TextField {
@Override
public void paint(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager colorManager, RowColLocation cursorLoc, int rowHeight) {
Rectangle clip, FieldBackgroundColorManager colorManager, RowColLocation cursorLoc,
int rowHeight) {
if (context.isPrinting()) {
print(g, context);
}
@ -341,7 +347,10 @@ public class ReverseClippingTextField implements TextField {
@Override
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) {
int column = textElement.getCharacterIndexForDataLocation(dataRow, dataColumn);
return new RowColLocation(0, Math.max(column, 0));
if (column < 0) {
return new DefaultRowColLocation();
}
return new RowColLocation(0, column);
}
private int findX(int col) {
@ -389,7 +398,9 @@ public class ReverseClippingTextField implements TextField {
@Override
public RowColLocation textOffsetToScreenLocation(int textOffset) {
int col = textOffset + startingCharIndex;
col = Math.max(col, 0);
if (col < 0) {
return new DefaultRowColLocation();
}
return new RowColLocation(0, Math.min(col, textElement.getText().length() - 1));
}

View file

@ -22,6 +22,7 @@ import javax.swing.JComponent;
import docking.widgets.fieldpanel.internal.FieldBackgroundColorManager;
import docking.widgets.fieldpanel.internal.PaintContext;
import docking.widgets.fieldpanel.support.DefaultRowColLocation;
import docking.widgets.fieldpanel.support.RowColLocation;
/**
@ -46,7 +47,8 @@ public class SimpleImageField implements Field {
* @param startY the starting y coordinate of the field.
* @param width the width of the field.
*/
public SimpleImageField(ImageIcon icon, FontMetrics metrics, int startX, int startY, int width) {
public SimpleImageField(ImageIcon icon, FontMetrics metrics, int startX, int startY,
int width) {
this(icon, metrics, startX, startY, width, false);
}
@ -115,6 +117,11 @@ public class SimpleImageField implements Field {
return height;
}
@Override
public int getNumDataRows() {
return 1;
}
@Override
public int getNumRows() {
return 1;
@ -180,7 +187,8 @@ public class SimpleImageField implements Field {
@Override
public void paint(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc, int rowHeight) {
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc,
int rowHeight) {
if (icon == null) {
return;
}
@ -263,7 +271,7 @@ public class SimpleImageField implements Field {
@Override
public RowColLocation textOffsetToScreenLocation(int textOffset) {
return new RowColLocation(0, 0);
return new DefaultRowColLocation();
}
@Override

View file

@ -110,6 +110,11 @@ public class SimpleTextField implements Field {
return startX;
}
@Override
public int getNumDataRows() {
return 1;
}
/**
*
* @see docking.widgets.fieldpanel.field.Field#getNumRows()
@ -199,7 +204,8 @@ public class SimpleTextField implements Field {
@Override
public void paint(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager colorManager, RowColLocation cursorLoc, int rowHeight) {
Rectangle clip, FieldBackgroundColorManager colorManager, RowColLocation cursorLoc,
int rowHeight) {
paintSelection(g, colorManager, 0);
paintHighlights(g, hlFactory.getHighlights(this, text, -1));
g.setFont(metrics.getFont());

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -24,75 +23,94 @@ import javax.swing.JComponent;
import docking.widgets.fieldpanel.support.RowColLocation;
/**
* Used to force a clip to happen when the max lines is exceeded in the VerticalLayoutTextField
* Used to force a clip to happen by using this field with space characters and size that far
* exceeds the available painting width.
*/
public class EmptyFieldElement implements FieldElement {
public class StrutFieldElement implements FieldElement {
private final int width;
public EmptyFieldElement(int width) {
public StrutFieldElement(int width) {
this.width = width;
}
@Override
public char charAt(int index) {
return ' ';
}
@Override
public int getCharacterIndexForDataLocation(int dataRow, int dataColumn) {
return 0;
}
public Color getColor(int charIndex) {
return Color.BLACK;
return -1; // we have not characters
}
@Override
public RowColLocation getDataLocationForCharacterIndex(int characterIndex) {
return new RowColLocation(0, 0);
}
public FieldElement getFieldElement(int column) {
@Override
public Color getColor(int charIndex) {
return Color.BLACK;
}
@Override
public FieldElement getFieldElement(int characterOffset) {
return this;
}
@Override
public int getHeightAbove() {
return 0;
}
@Override
public int getHeightBelow() {
return 0;
}
@Override
public int getMaxCharactersForWidth(int stringWidth) {
return 0;
}
@Override
public int getStringWidth() {
return width;
}
@Override
public String getText() {
return width == 0 ? "" : " ";
}
@Override
public int length() {
return width == 0 ? 0 : 1;
}
@Override
public void paint(JComponent c, Graphics g, int x, int y) {
// nothing to paint
}
@Override
public FieldElement replaceAll(char[] targets, char replacement) {
return this;
}
@Override
public FieldElement substring(int start) {
return new EmptyFieldElement(0);
return new StrutFieldElement(0);
}
@Override
public FieldElement substring(int start, int end) {
return new EmptyFieldElement(0);
return new StrutFieldElement(0);
}
@Override
public String toString() {
return ""; // empty text placeholder
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,13 +15,14 @@
*/
package docking.widgets.fieldpanel.field;
import docking.widgets.fieldpanel.support.DefaultRowColLocation;
import docking.widgets.fieldpanel.support.RowColLocation;
public interface TextField extends Field {
/**
* Sets this field to be primary such that its row is primary
* @param b this field to be primary such that its row is primary
*/
public void setPrimary(boolean b);
@ -38,12 +38,14 @@ public interface TextField extends Field {
* Translates a data row and column into a screen row and column.
* @param dataRow row as defined by the factory
* @param dataColumn the character offset into the dataRow
* @return row and column in the screen coordinate system.
* @return row and column in the screen coordinate system; a {@link DefaultRowColLocation} if
* this field does not contain the given column
*/
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn);
/**
* Returns true if the field is not displaying all the text information
* @return true if the field is not displaying all the text information
*/
public boolean isClipped();

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,27 +15,22 @@
*/
package docking.widgets.fieldpanel.field;
public final class TextFieldElement extends AbstractTextFieldElement {
public class TextFieldElement extends AbstractTextFieldElement {
public TextFieldElement(AttributedString attributedString, int row, int column) {
super(attributedString, row, column);
}
/**
* @see docking.widgets.fieldpanel.field.FieldElement#substring(int, int)
*/
@Override
public FieldElement substring(int start, int end) {
AttributedString as = attributedString.substring(start, end);
if ( as == attributedString ) {
if (as == attributedString) {
return this;
}
return new TextFieldElement(as, row, column+start);
return new TextFieldElement(as, row, column + start);
}
/**
* @see docking.widgets.fieldpanel.field.FieldElement#replaceAll(char[], char)
*/
@Override
public FieldElement replaceAll(char[] targets, char replacement) {
return new TextFieldElement(attributedString.replaceAll(targets, replacement), row, column);
}

View file

@ -16,25 +16,24 @@
package docking.widgets.fieldpanel.field;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.*;
import java.util.List;
import javax.swing.JComponent;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.fieldpanel.internal.FieldBackgroundColorManager;
import docking.widgets.fieldpanel.internal.PaintContext;
import docking.widgets.fieldpanel.support.*;
/**
* This class provides a TextField implementation that takes multiple FieldElements and places
* each on its own line within the field. It also can take a single FieldElements and
* word wrap,creating new FieldElements (one per line).
* each on its own line within the field.
*/
public class VerticalLayoutTextField implements TextField {
protected FieldElement[] textElements;
protected List<Field> subFields; // list of fields for FieldElements
protected List<TextField> subFields; // list of fields for FieldElements
protected int startX;
protected int width;
protected int preferredWidth;
@ -42,11 +41,34 @@ public class VerticalLayoutTextField implements TextField {
private int height;
private int heightAbove;
private int numDataRows;
private boolean isPrimary;
private String text;
// full text is all text with line separators, *but not with line delimiters*
private String fullText;
private List<String> lines;
// used in the getText() method to separate rows without adding newlines
private String rowSeparator;
protected boolean isClipped;
private String lineDelimiter; // used in the getText() method to separate lines
/**
* This constructor will create a text field from an array of FieldElements, putting each
* element on its own line.
*
* @param textElements the FieldElements to display
* @param startX the x position to draw the element
* @param width the max width allocated to this field
* @param maxLines the max number of lines to display
* @param hlFactory the highlight factory
* @deprecated use the constructor that takes a list
*/
@Deprecated(since = "10.1", forRemoval = true)
public VerticalLayoutTextField(FieldElement[] textElements, int startX, int width, int maxLines,
HighlightFactory hlFactory) {
this(Arrays.asList(textElements), startX, width, maxLines, hlFactory, " ");
}
/**
* This constructor will create a text field from an array of FieldElements, putting each
@ -58,7 +80,8 @@ public class VerticalLayoutTextField implements TextField {
* @param maxLines the max number of lines to display
* @param hlFactory the highlight factory
*/
public VerticalLayoutTextField(FieldElement[] textElements, int startX, int width, int maxLines,
public VerticalLayoutTextField(List<FieldElement> textElements, int startX, int width,
int maxLines,
HighlightFactory hlFactory) {
this(textElements, startX, width, maxLines, hlFactory, " ");
}
@ -72,25 +95,46 @@ public class VerticalLayoutTextField implements TextField {
* @param width the max width allocated to this field
* @param maxLines the max number of lines to display
* @param hlFactory the highlight factory
* @param lineDelimiter The string to space lines of text when concatenated by the
* @param rowSeparator The string used to space lines of text when concatenated by the
* getText() method.
*/
protected VerticalLayoutTextField(FieldElement[] textElements, int startX, int width,
int maxLines, HighlightFactory hlFactory, String lineDelimiter) {
protected VerticalLayoutTextField(List<FieldElement> textElements, int startX, int width,
int maxLines, HighlightFactory hlFactory, String rowSeparator) {
this.textElements = textElements;
this.startX = startX;
this.width = width;
this.hlFactory = hlFactory;
this.lineDelimiter = lineDelimiter;
this.rowSeparator = rowSeparator;
subFields = layoutElements(maxLines);
lines = generateLines(textElements);
fullText = generateText(textElements, rowSeparator);
subFields = layoutElements(textElements, maxLines);
numDataRows = textElements.size();
this.preferredWidth = calculatePreferredWidth();
preferredWidth = calculatePreferredWidth();
calculateHeight();
}
private List<String> generateLines(List<FieldElement> textElements) {
List<String> list = new ArrayList<>();
for (FieldElement field : textElements) {
list.add(field.getText());
}
return list;
}
private String generateText(List<FieldElement> elements, String delimiter) {
StringBuilder buf = new StringBuilder();
int n = elements.size() - 1;
for (int i = 0; i < n; i++) {
buf.append(elements.get(i).getText()).append(delimiter);
}
buf.append(elements.get(n).getText());
return buf.toString();
}
protected void calculateHeight() {
heightAbove = (subFields.get(0)).getHeightAbove();
for (Field field : subFields) {
@ -108,15 +152,12 @@ public class VerticalLayoutTextField implements TextField {
@Override
public String getText() {
if (text == null) {
text = generateText();
}
return text;
return fullText;
}
@Override
public String getTextWithLineSeparators() {
return generateText("\n");
return StringUtils.join(lines, '\n');
}
@Override
@ -144,6 +185,11 @@ public class VerticalLayoutTextField implements TextField {
return startX;
}
@Override
public int getNumDataRows() {
return numDataRows;
}
@Override
public int getNumRows() {
return subFields.size();
@ -157,11 +203,11 @@ public class VerticalLayoutTextField implements TextField {
@Override
public int getRow(int y) {
if (y < -heightAbove) {
if (y < 0) {
return 0;
}
int heightSoFar = -heightAbove;
int heightSoFar = 0;
int n = subFields.size();
for (int i = 0; i < n; i++) {
Field f = subFields.get(i);
@ -247,7 +293,7 @@ public class VerticalLayoutTextField implements TextField {
int startY = myStartY;
int translatedY = 0;
int extraSpace = rowSeparator.length();
for (int i = 0; i < n; i++) {
ClippingTextField subField = (ClippingTextField) subFields.get(i);
int subFieldHeight = subField.getHeight();
@ -276,7 +322,7 @@ public class VerticalLayoutTextField implements TextField {
startY += subFieldHeight;
g.translate(0, subFieldHeight);
translatedY += subFieldHeight;
columns += subField.getText().length() + lineDelimiter.length();
columns += subField.getText().length() + extraSpace;
}
// restore the graphics to where it was when we started.
@ -355,6 +401,7 @@ public class VerticalLayoutTextField implements TextField {
/**
* Returns the list of subfields in this field.
* @return the list of subfields in this field.
*/
public List<Field> getSubfields() {
return Collections.unmodifiableList(subFields);
@ -378,21 +425,23 @@ public class VerticalLayoutTextField implements TextField {
@Override
public FieldElement getFieldElement(int screenRow, int screenColumn) {
FieldElement clickedField = textElements[screenRow];
return clickedField.getFieldElement(screenColumn);
TextField f = subFields.get(screenRow);
int fieldRow = 0; // each field is on a single row
return f.getFieldElement(fieldRow, screenColumn);
}
protected List<Field> layoutElements(int maxLines) {
List<Field> newSubFields = new ArrayList<>();
protected List<TextField> layoutElements(List<FieldElement> textElements, int maxLines) {
List<TextField> newSubFields = new ArrayList<>();
boolean tooManyLines = textElements.length > maxLines;
boolean tooManyLines = textElements.size() > maxLines;
for (int i = 0; i < textElements.length && i < maxLines; i++) {
FieldElement element = textElements[i];
for (int i = 0; i < textElements.size() && i < maxLines; i++) {
FieldElement element = textElements.get(i);
if (tooManyLines && (i == maxLines - 1)) {
FieldElement[] elements = new FieldElement[2];
elements[0] = element;
elements[1] = new EmptyFieldElement(500);
elements[1] = new StrutFieldElement(500);
element = new CompositeFieldElement(elements);
}
TextField field = new ClippingTextField(startX, width, element, hlFactory);
@ -416,73 +465,60 @@ public class VerticalLayoutTextField implements TextField {
@Override
public RowColLocation screenToDataLocation(int screenRow, int screenColumn) {
screenRow = Math.min(screenRow, textElements.length - 1);
screenRow = Math.min(screenRow, subFields.size() - 1);
screenRow = Math.max(screenRow, 0);
screenColumn = Math.min(screenColumn, textElements[screenRow].length());
TextField field = subFields.get(screenRow);
screenColumn = Math.min(screenColumn, field.getText().length());
screenColumn = Math.max(screenColumn, 0);
return textElements[screenRow].getDataLocationForCharacterIndex(screenColumn);
int fieldRow = 0; // each field is on a single row
return field.screenToDataLocation(fieldRow, screenColumn);
}
/**
* Finds the corresponding row, column for string index, and offset
* @param dataRow index into the string array
* @param dataColumn offset into the indexed string.
*/
@Override
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) {
for (int screenRow = textElements.length - 1; screenRow >= 0; screenRow--) {
FieldElement element = textElements[screenRow];
int screenColumn = element.getCharacterIndexForDataLocation(dataRow, dataColumn);
if (screenColumn >= 0) {
return new RowColLocation(screenRow, screenColumn);
}
if (dataRow >= getNumRows()) {
TextField lastField = subFields.get(subFields.size());
return new DefaultRowColLocation(lastField.getText().length(), subFields.size() - 1);
}
return new RowColLocation(0, 0); // give up
}
protected String generateText() {
return generateText(lineDelimiter);
}
protected String generateText(String delimiter) {
StringBuffer buf = new StringBuffer();
int n = textElements.length - 1;
for (int i = 0; i < n; i++) {
buf.append(textElements[i].getText()).append(delimiter);
}
buf.append(textElements[n].getText());
return buf.toString();
TextField field = subFields.get(dataRow);
RowColLocation location = field.dataToScreenLocation(dataRow, dataColumn);
return location.withRow(dataRow);
}
@Override
public int screenLocationToTextOffset(int row, int col) {
if (row >= textElements.length) {
if (row >= subFields.size()) {
return getText().length();
}
int extraSpace = lineDelimiter.length();
int extraSpace = rowSeparator.length();
int len = 0;
for (int i = 0; i < row; i++) {
len += textElements[i].getText().length() + extraSpace;
len += lines.get(i).length() + extraSpace;
}
len += Math.min(col, textElements[row].getText().length());
len += Math.min(col, lines.get(row).length());
return len;
}
@Override
public RowColLocation textOffsetToScreenLocation(int textOffset) {
int extraSpace = lineDelimiter.length();
int n = textElements.length;
int absoluteOffset = textOffset;
int extraSpace = rowSeparator.length();
int n = subFields.size();
for (int i = 0; i < n; i++) {
int len = textElements[i].getText().length();
if (textOffset < len + extraSpace) {
return new RowColLocation(i, textOffset);
int len = lines.get(i).length();
if (absoluteOffset < len + extraSpace) {
return new RowColLocation(i, absoluteOffset);
}
textOffset -= len + extraSpace;
absoluteOffset -= len + extraSpace;
}
return new RowColLocation(n - 1, textElements[n - 1].getText().length());
int lastRow = n - 1;
int lastColumn = subFields.get(lastRow).getText().length();
return new DefaultRowColLocation(lastRow, lastColumn);
}
@Override

View file

@ -17,13 +17,16 @@ package docking.widgets.fieldpanel.field;
import docking.widgets.fieldpanel.support.*;
/**
* A text field meant to take a string of text and wrap as needed.
*/
public class WrappingVerticalLayoutTextField extends VerticalLayoutTextField {
/**
* This constructor will create a text field from an single AttributedString. The string will
* be word wrapped.
*
* @param textElement the AttributedString to display
* @param textElement the element to display
* @param startX the x position to draw the string
* @param width the max width allocated to this field
* @param maxLines the max number of lines to display
@ -31,12 +34,13 @@ public class WrappingVerticalLayoutTextField extends VerticalLayoutTextField {
*/
public WrappingVerticalLayoutTextField(FieldElement textElement, int startX, int width,
int maxLines, HighlightFactory hlFactory) {
super(FieldUtils.wrap(textElement, width), startX, width, maxLines, hlFactory, "");
super(FieldUtils.wrap(textElement, width), startX, width, maxLines, hlFactory, " ");
}
/**
* Create a text field from a single FieldElement. The text is wrapped, either an words or simply
* This constructor will create a text field from an single AttributedString. The string will
* be word wrapped.
*
* @param textElement is the element to display
* @param startX is the position to draw the string
* @param width is the max width allocated to this field
@ -47,16 +51,12 @@ public class WrappingVerticalLayoutTextField extends VerticalLayoutTextField {
public WrappingVerticalLayoutTextField(FieldElement textElement, int startX, int width,
int maxLines, HighlightFactory hlFactory, boolean breakOnWhiteSpace) {
super(FieldUtils.wrap(textElement, width, breakOnWhiteSpace), startX, width, maxLines,
hlFactory, "");
hlFactory, " ");
}
/**
* Finds the corresponding row, column for string index, and offset
* @param index index into the string array
* @param offset offset into the indexed string.
*/
@Override
public RowColLocation dataToScreenLocation(int index, int offset) {
return textOffsetToScreenLocation(offset);
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) {
// we represent one data row that may be split into multiple screen rows
return textOffsetToScreenLocation(dataColumn);
}
}

View file

@ -0,0 +1,41 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.fieldpanel.support;
/**
* A location used to represent a an edge case where not suitable location can be found and the
* client does not wish to return null.
*/
public class DefaultRowColLocation extends RowColLocation {
public DefaultRowColLocation() {
super(0, 0);
}
public DefaultRowColLocation(int row, int col) {
super(row, col);
}
@Override
public RowColLocation withCol(int newColumn) {
return new DefaultRowColLocation(row, newColumn);
}
@Override
public RowColLocation withRow(int newRow) {
return new DefaultRowColLocation(newRow, col);
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -38,6 +37,9 @@ public class FieldSelectionHelper {
/**
* Gets the selected text that pertains to an individual field. Null is returned if the
* given selection spans more than one field.
* @param selection the selection
* @param panel the field panel
* @return the text
*/
public static String getFieldSelectionText(FieldSelection selection, FieldPanel panel) {
if (!isStringSelection(selection)) {
@ -46,9 +48,14 @@ public class FieldSelectionHelper {
return getTextForField(selection.getFieldRange(0), panel);
}
/** Returns the text within the given selection. */
/**
* Returns the text within the given selection.
* @param selection the selection
* @param panel the field panel
* @return the text
*/
public static String getAllSelectedText(FieldSelection selection, FieldPanel panel) {
StringBuffer buffy = new StringBuffer();
StringBuilder buffy = new StringBuilder();
int numRanges = selection.getNumRanges();
for (int i = 0; i < numRanges; i++) {
FieldRange fieldRange = selection.getFieldRange(i);
@ -99,7 +106,7 @@ public class FieldSelectionHelper {
int startFieldNumber = startLoc.fieldNum;
BigInteger endIndex = endLoc.getIndex();
StringBuffer buffy = new StringBuffer();
StringBuilder buffy = new StringBuilder();
for (BigInteger i = startIndex; i.compareTo(endIndex) <= 0; i = i.add(BigInteger.ONE)) {
Layout layout = panel.getLayoutModel().getLayout(i);
String text = null;
@ -133,7 +140,7 @@ public class FieldSelectionHelper {
private static String getTextForFieldsInLayout(Layout layout, FieldRange fieldRange,
int startFieldNumber, int endFieldNumber) {
StringBuffer buffy = new StringBuffer();
StringBuilder buffy = new StringBuilder();
for (int i = startFieldNumber; i < endFieldNumber; i++) {
Field field = layout.getField(i);
buffy.append(field.getTextWithLineSeparators());

View file

@ -15,8 +15,7 @@
*/
package docking.widgets.fieldpanel.support;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import docking.widgets.fieldpanel.field.FieldElement;
@ -45,11 +44,11 @@ public class FieldUtils {
* @param width The maximum width to allow before wrapping
* @return The wrapped elements
*/
public static FieldElement[] wrap(FieldElement fieldElement, int width) {
public static List<FieldElement> wrap(FieldElement fieldElement, int width) {
FieldElement originalFieldElement = fieldElement.replaceAll(WHITE_SPACE, ' ');
if (originalFieldElement.getStringWidth() <= width) {
return new FieldElement[] { originalFieldElement };
return Arrays.asList(originalFieldElement);
}
List<FieldElement> lines = new ArrayList<>();
@ -63,7 +62,7 @@ public class FieldUtils {
wordWrapPos = findWordWrapPosition(originalFieldElement, width);
}
lines.add(originalFieldElement);
return lines.toArray(new FieldElement[lines.size()]);
return lines;
}
/**
@ -75,14 +74,14 @@ public class FieldUtils {
* @param breakOnWhiteSpace determines whether line breaks should happen at white space chars
* @return the wrapped elements
*/
public static FieldElement[] wrap(FieldElement fieldElement, int width,
public static List<FieldElement> wrap(FieldElement fieldElement, int width,
boolean breakOnWhiteSpace) {
if (breakOnWhiteSpace) {
return wrap(fieldElement, width);
}
FieldElement originalFieldElement = fieldElement.replaceAll(WHITE_SPACE, ' ');
if (originalFieldElement.getStringWidth() <= width) {
return new FieldElement[] { originalFieldElement };
return Arrays.asList(originalFieldElement);
}
List<FieldElement> lines = new ArrayList<>();
@ -99,7 +98,7 @@ public class FieldUtils {
}
}
lines.add(originalFieldElement);
return lines.toArray(new FieldElement[lines.size()]);
return lines;
}
/**
@ -133,10 +132,11 @@ public class FieldUtils {
/**
* Finds the position within the given element at which to split the line for word wrapping.
* This method only breaks on whitespace characters. It finds the last whitespace character
* that completely fits within the given width. If there is no whitespace character before
* the width break point, it finds the first whitespace character after the width. If the
* element cannot be split at all, it returns 0.
* This method finds the last whitespace character that completely fits within the given width.
* If there is no whitespace character before the width break point, it finds the first
* whitespace character after the width. If no whitespace can be found, then the text will
* be split at a non-whitespace character.
*
* @param element the element to split
* @param width the max width to allow before looking for a word wrap positions
* @return 0 if the element cannot be split, else the character position of the string
@ -156,28 +156,17 @@ public class FieldUtils {
}
return wrapPosition;
// The following code was replace with the return just above. This has the effect
// of splitting contiguous words at the field width instead of at the next white
// space beyond.
// whiteSpacePosition = text.indexOf(" ", wrapPosition);
// if (whiteSpacePosition >= 0) {
// if (whiteSpacePosition + 1 >= element.length()) { // if whitespace at end, no split
// return 0;
// }
// return whiteSpacePosition;
// }
// return 0;
}
/**
* Trims "goofy" characters off of the given label, like spaces, '[',']', etc.
* Trims unwanted characters off of the given label, like spaces, '[',']', etc.
* @param string The string to be trimmed
* @return The trimmed string.
*/
public static String trimString(String string) {
// short-circuit case where the given string starts normally, but contains invalid
// characters (e.g., param_1[EAX])
StringBuffer buffer = new StringBuffer(string);
StringBuilder buffer = new StringBuilder(string);
if (Character.isJavaIdentifierPart(buffer.charAt(0))) {
// in this case just take all valid characters and then exit
for (int index = 1; index < buffer.length(); index++) {

View file

@ -19,8 +19,9 @@ package docking.widgets.fieldpanel.support;
* Simple class to return a row, column location.
*/
public class RowColLocation {
private int row;
private int col;
protected int row;
protected int col;
/**
* Constructs a new RowColLocation with the given row and column.
* @param row the row location
@ -30,36 +31,48 @@ public class RowColLocation {
this.row = row;
this.col = col;
}
/**
* Returns the row.
*/
public int row() {
return row;
}
/**
* Returns the column.
*/
public int col() {
return col;
}
/**
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "RowColLocation("+row+","+col+")";
public RowColLocation withCol(int newColumn) {
return new RowColLocation(row, newColumn);
}
public RowColLocation withRow(int newRow) {
return new RowColLocation(newRow, col);
}
@Override
public boolean equals( Object object ) {
public String toString() {
return row + "," + col;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + col;
result = prime * result + row;
return result;
}
@Override
public boolean equals(Object object) {
if (object == null) {
return false;
}
if ( object.getClass() == RowColLocation.class ) {
if (!getClass().equals(object.getClass())) {
return false;
}
RowColLocation loc = (RowColLocation) object;
return (row == loc.row) && (col == loc.col);
}
return false;
}
}

View file

@ -180,7 +180,8 @@ public class RowLayout implements Layout {
gapIndex = fields.length;
}
int startX =
gapIndex == 0 ? rect.x : fields[gapIndex - 1].getStartX() +
gapIndex == 0 ? rect.x
: fields[gapIndex - 1].getStartX() +
fields[gapIndex - 1].getWidth();
int endX = gapIndex >= fields.length ? rect.x + rect.width : fields[gapIndex].getStartX();
@ -201,7 +202,7 @@ public class RowLayout implements Layout {
Field field = fields[index];
cursorLoc.fieldNum = index;
cursorLoc.row = field.getRow(y - heightAbove);
cursorLoc.row = field.getRow(y);
cursorLoc.col = field.getCol(cursorLoc.row, x);
return field.getX(cursorLoc.row, cursorLoc.col);
}

View file

@ -18,6 +18,8 @@ package docking.widgets.fieldpanel;
import static org.junit.Assert.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
@ -32,10 +34,6 @@ public class FlowLayoutTextFieldTest extends AbstractGenericTest {
private FlowLayoutTextField textField;
public FlowLayoutTextFieldTest() {
super();
}
@SuppressWarnings("deprecation") // we mean to use getFontMetrics
@Before
public void setUp() throws Exception {
@ -47,156 +45,18 @@ public class FlowLayoutTextFieldTest extends AbstractGenericTest {
Font font = new Font("Times New Roman", 0, 14);
Toolkit tk = Toolkit.getDefaultToolkit();
FontMetrics fm = tk.getFontMetrics(font);
FieldElement[] elements = new FieldElement[4];
elements[0] = new TextFieldElement(new AttributedString("Hello ", Color.BLUE, fm), 0, 0);
elements[1] = new TextFieldElement(
new AttributedString("World ", Color.RED, fm, true, Color.BLUE), 1, 0);
elements[2] =
new TextFieldElement(new AttributedString(CLIPPED_STRING, Color.GREEN, fm), 2, 0);
elements[3] = new TextFieldElement(new AttributedString("Wow! ", Color.GRAY, fm), 3, 0);
List<FieldElement> elements = new ArrayList<>();
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));
elements.add(
new TextFieldElement(new AttributedString(CLIPPED_STRING, Color.GREEN, fm), 2, 0));
elements.add(new TextFieldElement(new AttributedString("Wow! ", Color.GRAY, fm), 3, 0));
textField = new FlowLayoutTextField(elements, 100, 100, 3, factory);
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getWidth()'
*/
@Test
public void testGetWidth() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getHeight()'
*/
@Test
public void testGetHeight() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getStartX()'
*/
@Test
public void testGetStartX() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getNumRows()'
*/
@Test
public void testGetNumRows() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getNumCols(int)'
*/
@Test
public void testGetNumCols() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getRow(int)'
*/
@Test
public void testGetRow() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getCol(int, int)'
*/
@Test
public void testGetCol() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getY(int)'
*/
@Test
public void testGetY() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getX(int, int)'
*/
@Test
public void testGetX() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.isValid(int, int)'
*/
@Test
public void testIsValid() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.paint(Graphics, PaintContext, boolean)'
*/
@Test
public void testPaint() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.paintCursor(Graphics, PaintContext, boolean, int, int)'
*/
@Test
public void testPaintCursor() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getCursorBounds(int, int)'
*/
@Test
public void testGetCursorBounds() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.contains(int, int)'
*/
@Test
public void testContains() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getScrollableUnitIncrement(int, int, int)'
*/
@Test
public void testGetScrollableUnitIncrement() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.isPrimary()'
*/
@Test
public void testIsPrimary() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.setPrimary(boolean)'
*/
@Test
public void testSetPrimary() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getStringLocation(int, int)'
*/
@Test
public void testScreenToDataLocation() {
assertEquals(new RowColLocation(0, 0), textField.screenToDataLocation(0, 0));
@ -222,9 +82,6 @@ public class FlowLayoutTextFieldTest extends AbstractGenericTest {
assertEquals(new RowColLocation(3, 5), textField.screenToDataLocation(50, 75));
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getWrappedLocation(int, int)'
*/
@Test
public void testDataToScreenLocation() {
assertEquals(new RowColLocation(0, 0), textField.dataToScreenLocation(0, 0));
@ -247,9 +104,6 @@ public class FlowLayoutTextFieldTest extends AbstractGenericTest {
assertEquals(new RowColLocation(0, 0), textField.dataToScreenLocation(0, 75));
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getRowColumn(int)'
*/
@Test
public void testGetRowColumn() {
assertEquals(new RowColLocation(0, 0), textField.textOffsetToScreenLocation(0));
@ -264,55 +118,5 @@ public class FlowLayoutTextFieldTest extends AbstractGenericTest {
assertEquals(new RowColLocation(1, 18), textField.textOffsetToScreenLocation(30));
assertEquals(new RowColLocation(2, 5), textField.textOffsetToScreenLocation(1000));
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getSubfields()'
*/
@Test
public void testGetSubfields() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getHeightAbove()'
*/
@Test
public void testGetHeightAbove() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getHeightBelow()'
*/
@Test
public void testGetHeightBelow() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.rowHeightChanged(int, int)'
*/
@Test
public void testRowHeightChanged() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getText()'
*/
@Test
public void testGetText() {
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getTextOffset(int, int)'
*/
@Test
public void testGetTextOffset() {
}
}

View file

@ -18,6 +18,8 @@ package docking.widgets.fieldpanel;
import static org.junit.Assert.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
@ -32,10 +34,6 @@ public class VerticalLayoutTextFieldTest extends AbstractGenericTest {
private VerticalLayoutTextField textField;
public VerticalLayoutTextFieldTest() {
super();
}
@SuppressWarnings("deprecation") // we mean to use getFontMetrics
@Before
public void setUp() throws Exception {
@ -47,20 +45,19 @@ public class VerticalLayoutTextFieldTest extends AbstractGenericTest {
Font font = new Font("Times New Roman", 0, 14);
Toolkit tk = Toolkit.getDefaultToolkit();
FontMetrics fm = tk.getFontMetrics(font);
FieldElement[] elements = new FieldElement[4];
elements[0] = new TextFieldElement(new AttributedString("Hello", Color.BLUE, fm), 0, 0);
elements[1] = new TextFieldElement(
new AttributedString("World", Color.RED, fm, true, Color.BLUE), 1, 0);
elements[2] =
new TextFieldElement(new AttributedString(CLIPPED_STRING, Color.GREEN, fm), 2, 0);
elements[3] = new TextFieldElement(new AttributedString("Wow!", Color.GRAY, fm), 3, 0);
List<FieldElement> elements = new ArrayList<>();
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));
elements.add(
new TextFieldElement(new AttributedString(CLIPPED_STRING, Color.GREEN, fm), 2, 0));
elements.add(new TextFieldElement(new AttributedString("Wow!", Color.GRAY, fm), 3, 0));
textField = new VerticalLayoutTextField(elements, 100, 100, 5, factory);
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getStringLocation(int, int)'
*/
@Test
public void testScreenToDataLocation() {
assertEquals(new RowColLocation(0, 0), textField.screenToDataLocation(0, 0));
@ -81,11 +78,8 @@ public class VerticalLayoutTextFieldTest extends AbstractGenericTest {
assertEquals(new RowColLocation(3, 4), textField.screenToDataLocation(50, 75));
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getWrappedLocation(int, int)'
*/
@Test
public void testGetWrappedLocation() {
public void testDataToScreenLocation() {
assertEquals(new RowColLocation(0, 0), textField.dataToScreenLocation(0, 0));
assertEquals(new RowColLocation(0, 2), textField.dataToScreenLocation(0, 2));
assertEquals(new RowColLocation(0, 5), textField.dataToScreenLocation(0, 5));
@ -96,26 +90,26 @@ public class VerticalLayoutTextFieldTest extends AbstractGenericTest {
assertEquals(new RowColLocation(2, 0), textField.dataToScreenLocation(2, 0));
assertEquals(new RowColLocation(2, 4), textField.dataToScreenLocation(2, 4));
assertEquals(new RowColLocation(2, 15), textField.dataToScreenLocation(2, 15));
assertEquals(new RowColLocation(2, 12), textField.dataToScreenLocation(2, 12));
assertEquals(new DefaultRowColLocation(2, 12), textField.dataToScreenLocation(2, 15));
assertEquals(new RowColLocation(3, 0), textField.dataToScreenLocation(3, 0));
assertEquals(new RowColLocation(3, 4), textField.dataToScreenLocation(3, 4));
}
/*
* Test method for 'ghidra.util.bean.field.WrappingTextField.getRowColumn(int)'
*/
@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(1, 0), textField.textOffsetToScreenLocation(6));
assertEquals(new RowColLocation(1, 4), textField.textOffsetToScreenLocation(10));
assertEquals(new RowColLocation(1, 5), textField.textOffsetToScreenLocation(11));
assertEquals(new RowColLocation(2, 0), textField.textOffsetToScreenLocation(12));
assertEquals(new RowColLocation(1, 4), textField.textOffsetToScreenLocation(10));
assertEquals(new RowColLocation(3, 4), textField.textOffsetToScreenLocation(1000));
assertEquals(new DefaultRowColLocation(3, 4), textField.textOffsetToScreenLocation(1000));
}
}

View file

@ -0,0 +1,586 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.fieldpanel.field;
import static org.junit.Assert.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import docking.widgets.fieldpanel.support.*;
import generic.test.AbstractGenericTest;
public class CompositeVerticalLayoutTextFieldTest extends AbstractGenericTest {
private static final String LONG_STRING = "Supercalifragilisticexpialidocious";
private FontMetrics fontMetrics;
private CompositeVerticalLayoutTextField field;
private List<String> rows;
// some default field values
int startX = 100; // arbitrary
int width = 100; // used to trigger horizontal clipping
int maxLines = 5; // used to trigger vertical clipping
private HighlightFactory hlFactory = (hlField, text, cursorTextOffset) -> {
return new Highlight[] {};
};
@Before
public void setUp() throws Exception {
Font font = new Font("Times New Roman", 0, 14);
fontMetrics = getFontMetrics(font);
field = createField(maxLines, List.of(
"Hello",
"World",
LONG_STRING,
"Wow!"));
}
private CompositeVerticalLayoutTextField createField(int lineLimit, List<String> lines) {
rows = lines;
List<FieldElement> elements = new ArrayList<>();
int row = 0;
for (String line : lines) {
elements.add(createRow(row++, line, Color.BLUE));
}
List<TextField> fields = new ArrayList<>();
for (FieldElement element : elements) {
fields.add(new ClippingTextField(startX, width, element, hlFactory));
}
return new CompositeVerticalLayoutTextField(fields, startX, width, lineLimit, hlFactory);
}
private CompositeVerticalLayoutTextField createBasicWrappingField(List<String> lines) {
rows = lines;
List<FieldElement> elements = new ArrayList<>();
int row = 0;
for (String line : lines) {
elements.add(createRow(row++, line, Color.BLUE));
}
List<TextField> fields = new ArrayList<>();
for (FieldElement element : elements) {
fields.add(new WrappingVerticalLayoutTextField(element, startX, width, maxLines,
hlFactory));
}
return new CompositeVerticalLayoutTextField(fields, startX, width, maxLines, hlFactory);
}
private CompositeVerticalLayoutTextField createMixedWrappingField(List<String> lines) {
rows = lines;
int row = 0;
List<TextField> fields = new ArrayList<>();
fields.add(wrappedField(row++, lines.get(0)));
fields.add(clippedField(row++, lines.get(1)));
fields.add(wrappedField(row++, lines.get(2)));
return new CompositeVerticalLayoutTextField(fields, startX, width, maxLines, hlFactory);
}
private CompositeVerticalLayoutTextField createMixedWrappingField(TextField... fields) {
return new CompositeVerticalLayoutTextField(Arrays.asList(fields), startX, width, maxLines,
hlFactory);
}
private TextField wrappedField(int row, String text) {
FieldElement element = createRow(row, text, Color.BLUE);
return new WrappingVerticalLayoutTextField(element, startX, width, maxLines, hlFactory);
}
private TextField clippedField(int row, String text) {
FieldElement element = createRow(row, text, Color.BLUE);
return new ClippingTextField(startX, width, element, hlFactory);
}
private FieldElement createRow(int row, String text, Color color) {
return new TextFieldElement(new AttributedString(text, color, fontMetrics), row, 0);
}
@Test
public void testScreenToDataLocation() {
assertRowCol(0, 0, field.screenToDataLocation(0, 0));
assertRowCol(0, 2, field.screenToDataLocation(0, 2));
assertRowCol(0, 5, field.screenToDataLocation(0, 5));
assertRowCol(0, 5, field.screenToDataLocation(0, 6)); // past end
assertRowCol(0, 5, field.screenToDataLocation(0, 75));
assertRowCol(1, 0, field.screenToDataLocation(1, 0));
assertRowCol(1, 5, field.screenToDataLocation(1, 6));
assertRowCol(1, 5, field.screenToDataLocation(1, 16));
assertRowCol(2, 0, field.screenToDataLocation(2, 0));
assertRowCol(2, 4, field.screenToDataLocation(2, 4));
assertRowCol(2, 34, field.screenToDataLocation(2, 75));
assertRowCol(3, 0, field.screenToDataLocation(3, 0));
assertRowCol(3, 4, field.screenToDataLocation(50, 75));
}
@Test
public void testDataToScreenLocation() {
assertRowCol(0, 0, field.dataToScreenLocation(0, 0));
assertRowCol(0, 2, field.dataToScreenLocation(0, 2));
assertRowCol(0, 5, field.dataToScreenLocation(0, 5));
assertRowCol(1, 0, field.dataToScreenLocation(1, 0));
assertRowCol(1, 4, field.dataToScreenLocation(1, 4));
assertRowCol(1, 5, field.dataToScreenLocation(1, 5));
assertRowCol(2, 0, field.dataToScreenLocation(2, 0));
assertRowCol(2, 4, field.dataToScreenLocation(2, 4));
assertRowCol(2, 12, field.dataToScreenLocation(2, 12));
assertRowCol(2, 12, field.dataToScreenLocation(2, 15));
assertRowCol(3, 0, field.dataToScreenLocation(3, 0));
assertRowCol(3, 4, field.dataToScreenLocation(3, 4));
}
@Test
public void testTextOffsetToScreenLocation() {
//
// Each row of text has text.lenghth() + 1 possible positions: before and after each
// character. For example, in the text "hi", these are the possible cursor positions:
//
// |hi
// h|i
// hi|
//
// each line may have a line separator
int separator = field.getRowSeparator().length();
// dumpFieldOffsets();
// the end is after the last character
String row1 = rows.get(0);
int row1End = row1.length();
assertRowCol(0, 0, field.textOffsetToScreenLocation(0));
assertRowCol(0, row1End - 1, field.textOffsetToScreenLocation(row1End - 1));
assertRowCol(0, row1End, field.textOffsetToScreenLocation(row1End));
int row2Start = row1End + separator;
String row2 = rows.get(1);
int row2End = row2Start + row2.length();
int relativeEnd = row2End - row2Start;
assertRowCol(1, 0, field.textOffsetToScreenLocation(row2Start));
assertRowCol(1, relativeEnd - 1, field.textOffsetToScreenLocation(row2End - 1));
assertRowCol(1, relativeEnd, field.textOffsetToScreenLocation(row2End));
String row3 = rows.get(2);
int row3Start = row2End + separator;
assertRowCol(2, 0, field.textOffsetToScreenLocation(row3Start));
int row3End = row3Start + row3.length();
int row4Start = row3End + 1;
assertRowCol(3, 0, field.textOffsetToScreenLocation(row4Start));
// far past the end will put the cursor at the end
String row4 = rows.get(3);
assertRowCol(3, row4.length(), field.textOffsetToScreenLocation(1000));
}
@Test
public void testScreenLocationToTextOffset() {
// each line may have a line separator
int separator = field.getRowSeparator().length();
String row1 = rows.get(0);
int row1End = row1.length();
assertEquals(0, field.screenLocationToTextOffset(0, 0));
assertEquals(row1End - 1, field.screenLocationToTextOffset(0, row1End - 1));
assertEquals(row1End, field.screenLocationToTextOffset(0, row1End));
int row2Start = row1End + separator;
String row2 = rows.get(1);
int row2End = row2Start + row2.length();
int relativeEnd = row2End - row2Start;
assertEquals(row2Start, field.screenLocationToTextOffset(1, 0));
assertEquals(row2End - 1, field.screenLocationToTextOffset(1, relativeEnd - 1));
assertEquals(row2End, field.screenLocationToTextOffset(1, relativeEnd));
String row3 = rows.get(2);
int row3Start = row2End + separator;
assertEquals(row3Start, field.screenLocationToTextOffset(2, 0));
int row3End = row3Start + row3.length();
int row4Start = row3End + 1;
assertEquals(row4Start, field.screenLocationToTextOffset(3, 0));
assertRowCol(3, 0, field.textOffsetToScreenLocation(row4Start));
// far past the end will put the cursor at the end
String row4 = rows.get(3);
int row4End = row4Start + row4.length();
assertEquals(row4End, field.screenLocationToTextOffset(3, 1000));
}
@Test
public void testGetFieldElement() {
String row1 = rows.get(0);
assertEquals(row1, field.getFieldElement(0, 0).toString());
assertEquals(row1, field.getFieldElement(0, 1).toString());
assertEquals(row1, field.getFieldElement(0, row1.length()).toString());
assertEquals(row1, field.getFieldElement(0, row1.length() + 1).toString());
assertEquals(row1, field.getFieldElement(0, 100).toString());
String row2 = rows.get(1);
assertEquals(row2, field.getFieldElement(1, 0).toString());
assertEquals(row2, field.getFieldElement(1, 1).toString());
assertEquals(row2, field.getFieldElement(1, row2.length()).toString());
assertEquals(row2, field.getFieldElement(1, row2.length() + 1).toString());
assertEquals(row2, field.getFieldElement(1, 100).toString());
String row3 = rows.get(2);
assertEquals(row3, field.getFieldElement(2, 0).toString());
assertEquals(row3, field.getFieldElement(2, 1).toString());
assertEquals(row3, field.getFieldElement(2, row3.length()).toString());
assertEquals(row3, field.getFieldElement(2, row3.length() + 1).toString());
assertEquals(row3, field.getFieldElement(2, 100).toString());
String row4 = rows.get(3);
assertEquals(row4, field.getFieldElement(3, 0).toString());
assertEquals(row4, field.getFieldElement(3, 1).toString());
assertEquals(row4, field.getFieldElement(3, row4.length()).toString());
assertEquals(row4, field.getFieldElement(3, row4.length() + 1).toString());
assertEquals(row4, field.getFieldElement(3, 100).toString());
}
@Test
public void testGetNumColumns() {
int separator = field.getRowSeparator().length();
String row1 = rows.get(0);
int row1Columns = row1.length() + separator;
assertEquals(row1Columns, field.getNumCols(0));
String row2 = rows.get(1);
int row2Columns = row2.length() + separator;
assertEquals(row2Columns, field.getNumCols(1));
// note: the number of columns is the clipped text length, which is 12, plus 1 extra
// column to allow for placing the cursor after the text
int clippedLength = 13; // not sure how to get this from the field
assertEquals(clippedLength, field.getNumCols(2));
String row4 = rows.get(3);
int row4Columns = row4.length() + separator;
assertEquals(row4Columns, field.getNumCols(3));
}
@Test
public void testClippingWithTooManyRows() {
int lineLimit = 2;
field = createField(lineLimit, List.of(
"Hello",
"Wolrd",
LONG_STRING,
"Wow!"));
assertEquals(2, field.getNumRows());
assertEquals(4, field.getNumDataRows());
assertRowCol(0, 0, field.dataToScreenLocation(0, 0));
assertRowCol(0, 2, field.dataToScreenLocation(0, 2));
assertRowCol(0, 5, field.dataToScreenLocation(0, 5));
assertRowCol(1, 0, field.dataToScreenLocation(1, 0));
assertRowCol(1, 4, field.dataToScreenLocation(1, 4));
assertRowCol(1, 5, field.dataToScreenLocation(1, 5));
// try accessing clipped rows
assertRowCol(1, 0, field.dataToScreenLocation(2, 0));
assertRowCol(1, 5, field.dataToScreenLocation(2, 5));
assertRowCol(1, 5, field.dataToScreenLocation(20, 50));
}
@Test
public void testIsClipped_NoClipping() {
field = createField(maxLines, List.of("Hello", "Wolrd"));
assertFalse(field.isClipped());
}
@Test
public void testIsClipped_HorizontalClipping() {
field = createField(maxLines, List.of(LONG_STRING));
assertTrue(field.isClipped());
}
@Test
public void testIsClipped_VerticalClipping() {
int lineLimit = 2;
field = createField(lineLimit, List.of(
"Hello",
"Wolrd",
"Wow!"));
assertTrue(field.isClipped());
}
@Test
public void testIsClipped_HorizontalAndVerticalClipping() {
int lineLimit = 2;
field = createField(lineLimit, List.of(
"Hello",
"Wolrd",
LONG_STRING,
"Wow!"));
assertTrue(field.isClipped());
}
@Test
public void getGetAllRows() {
String row1 = rows.get(0);
List<TextField> allRows = field.getAllRowsUpTo(0);
assertEquals(1, allRows.size());
assertEquals(row1, allRows.get(0).toString());
String row2 = rows.get(1);
allRows = field.getAllRowsUpTo(1);
assertEquals(2, allRows.size());
assertEquals(row1, allRows.get(0).toString());
assertEquals(row2, allRows.get(1).toString());
String row3 = rows.get(2);
allRows = field.getAllRowsUpTo(2);
assertEquals(3, allRows.size());
assertEquals(row1, allRows.get(0).toString());
assertEquals(row2, allRows.get(1).toString());
assertEquals(row3, allRows.get(2).toString());
String row4 = rows.get(3);
allRows = field.getAllRowsUpTo(3);
assertEquals(4, allRows.size());
assertEquals(row1, allRows.get(0).toString());
assertEquals(row2, allRows.get(1).toString());
assertEquals(row3, allRows.get(2).toString());
assertEquals(row4, allRows.get(3).toString());
allRows = field.getAllRowsUpTo(10);
assertEquals(4, allRows.size());
allRows = field.getAllRowsUpTo(-1);
assertEquals(0, allRows.size());
}
@Test
public void testGetText() {
assertEquals("Hello World Supercalifragilisticexpialidocious Wow! ", field.getText());
}
@Test
public void testGetTextWithLineSeparators() {
assertEquals("Hello\nWorld\nSupercalifragilisticexpialidocious\nWow!",
field.getTextWithLineSeparators());
}
@Test
public void testGetY_And_GetRow() {
int y = field.getY(0);
int row = field.getRow(y);
assertEquals("Wrong row for y value: " + y, 0, row);
y = field.getY(1);
row = field.getRow(y);
assertEquals("Wrong row for y value: " + y, 1, row);
y = field.getY(2);
row = field.getRow(y);
assertEquals("Wrong row for y value: " + y, 2, row);
y = field.getY(3);
row = field.getRow(y);
assertEquals("Wrong row for y value: " + y, 3, row);
// try values past the end
int yForRowTooBig = field.getY(10);
assertEquals(y, yForRowTooBig);
int rowForYTooBig = field.getRow(1000);
assertEquals(3, rowForYTooBig);
// try values before the beginning
int yForRowTooSmall = field.getY(-1);
int expectedY = -field.getHeightAbove();
assertEquals(expectedY, yForRowTooSmall);
int rowForYTooSmall = field.getRow(-1000);
assertEquals(0, rowForYTooSmall);
}
@Test
public void testGetX_And_GetCol() {
String row1 = rows.get(0);
int x = field.getX(0, 0);
int column = field.getCol(0, x);
assertEquals(0, column);
x = field.getX(0, row1.length());
column = field.getCol(0, x);
assertEquals(row1.length(), column);
String row2 = rows.get(1);
x = field.getX(1, 0);
column = field.getCol(1, x);
assertEquals(0, column);
x = field.getX(1, row2.length());
column = field.getCol(1, x);
assertEquals(row2.length(), column);
String row3 = rows.get(2);
x = field.getX(2, 0);
column = field.getCol(2, x);
assertEquals(0, column);
x = field.getX(2, row3.length());
column = field.getCol(2, x);
int clippedLength = 12; // not sure how to get this from the field
assertEquals(clippedLength, column);
x = field.getX(2, row3.length() + 1);
column = field.getCol(2, x);
assertEquals(clippedLength, column);
}
@Test
public void testLayoutWithWrapping_OneWrappedRow() {
//
// Test the composite field when one of the internal fields will wrap into multiple rows
// when too long.
//
field =
createBasicWrappingField(List.of("This is a line with multiple words for wrapping"));
assertEquals(4, field.getNumRows());
assertEquals("This is a line", field.getFieldElement(0, 0).getText());
assertEquals("with multiple", field.getFieldElement(1, 0).getText());
assertEquals("words for", field.getFieldElement(2, 0).getText());
assertEquals("wrapping", field.getFieldElement(3, 0).getText());
// note: the final 'data' row becomes 4 'screen' rows
assertEquals(15, field.getNumCols(0));
assertEquals(14, field.getNumCols(1));
assertEquals(10, field.getNumCols(2));
assertEquals(9, field.getNumCols(3));
}
@Test
public void testLayoutWrapping_TwoWrappedRow() {
field = createBasicWrappingField(List.of("This is line one", "This is line two"));
assertEquals(4, field.getNumRows());
assertEquals("This is line", field.getFieldElement(0, 0).getText());
assertEquals("one", field.getFieldElement(1, 0).getText());
assertEquals("This is line", field.getFieldElement(2, 0).getText());
assertEquals("two", field.getFieldElement(3, 0).getText());
// note: the final 'data' row becomes 4 'screen' rows
assertEquals(13, field.getNumCols(0));
assertEquals(4, field.getNumCols(1));
assertEquals(13, field.getNumCols(2));
assertEquals(4, field.getNumCols(3));
}
@Test
public void testLayoutWrapping_MixedRows() {
//
// Test that we can mix wrapping and non-wrapping rows
//
field = createMixedWrappingField(
List.of("This is line one", "This line does not wrap", "This is line two"));
assertEquals(5, field.getNumRows());
assertEquals("This is line", field.getFieldElement(0, 0).getText());
assertEquals("one", field.getFieldElement(1, 0).getText());
assertEquals("This line does not wrap", field.getFieldElement(2, 0).getText());
assertEquals("This is line", field.getFieldElement(3, 0).getText());
assertEquals("two", field.getFieldElement(4, 0).getText());
// note: the final 'data' row becomes 5 'screen' rows
assertEquals(13, field.getNumCols(0));
assertEquals(4, field.getNumCols(1));
assertEquals(14, field.getNumCols(2));
assertEquals(13, field.getNumCols(3));
assertEquals(4, field.getNumCols(4));
}
@Test
public void testLayoutWrapping_MixedRows_TrailingWrappingRow() {
String row1 = "1: clipped row: This will be clipped horizonally";
String row2 = "2: clipped row: This will be clipped horizonally";
String row3 = "3: wrapped row: This field will wrap";
TextField field1 = clippedField(0, row1);
TextField field2 = clippedField(1, row2);
TextField field3 = wrappedField(2, row3);
field = createMixedWrappingField(field1, field2, field3);
assertEquals(5, field.getNumRows());
assertEquals(row1, field.getFieldElement(0, 0).getText());
assertEquals(row2, field.getFieldElement(1, 0).getText());
assertEquals("3: wrapped", field.getFieldElement(2, 0).getText());
assertEquals("row: This field", field.getFieldElement(3, 0).getText());
assertEquals("will wrap", field.getFieldElement(4, 0).getText());
// not sure how to get this from the field
int clippedLength = 14;
assertEquals(clippedLength, field.getNumCols(0));
assertEquals(clippedLength, field.getNumCols(1));
// note: the final 'data' row becomes 3 'screen' rows
assertEquals(11, field.getNumCols(2));
assertEquals(16, field.getNumCols(3));
assertEquals(10, field.getNumCols(4));
}
private void assertRowCol(int expectedRow, int expectedColumn, RowColLocation actualLocation) {
assertEquals("Wrong row", expectedRow, actualLocation.row());
assertEquals("Wrong column", expectedColumn, actualLocation.col());
}
}

View file

@ -19,6 +19,7 @@ import static org.junit.Assert.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
@ -1561,6 +1562,19 @@ public abstract class AbstractGenericTest extends AbstractGTest {
UIManager.put("TextArea.font", f);
}
/**
* Returns a font metrics for the given font using a generic buffered image graphics context.
* @param font the font
* @return the font metrics
*/
public static FontMetrics getFontMetrics(Font font) {
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = image.getGraphics();
FontMetrics fm = g.getFontMetrics(font);
g.dispose();
return fm;
}
/**
* Signals that the client expected the System Under Test (SUT) to report errors. Use this
* when you wish to verify that errors are reported and you do not want those errors to