diff --git a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm
index 2b7eccdf47..5f988a63af 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm
@@ -927,22 +927,43 @@
Display Local Block - Prepends the name of the memory block containing the XREF
source address to each XREF.
+ Namespace Options:
+
+
+ Display Non-local Namespace - Select this option to prepend the namespace to all
+ XREFs that are not from an instruction within the current Function's body. Currently,
+ this would only affect XREFs that originate in some other function.
+
+
+ Display Library in Namespace - Include the library name in the namespace.
+
+
+ Display Local Namespace - Select this option to prepend the namespace to all
+ XREFs that are from the current Function.
+
+
+ Use Local Namespace Override - Select this
+ option to show a fixed prefix for local XREFs instead of the function's name. This
+ option is only available if the "Display Local Namespace" option is on. The text box
+ contains the prefix to use for local XREFs.
+
+
+
+ Display Reference Type - Shows a single letter to represent the type of reference.
+ Some of the possible types are:
+ Read (R), Write (W), Data (*), Call (c), Jump (j) and Thunk (T)
.
+
+
Group by Function - 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.
+
Maximum Number of XREFs To Display - The maximum number of lines used to display
XREFs. Additional XREFs will not be displayed.
-
- Display Non-local Namespace - Select this option to prepend the namespace to all
- XREFs that are not from an instruction within the current Function's body. Currently,
- this would only affect XREFs that originate in some other function.
-
-
- Display Local Namespace - Select this option to prepend the namespace to all
- XREFs that are from the current Function.
-
-
- Use Local Namespace Override - Select this
- option to show a fixed prefix for local XREFs instead of the function's name. This
- option is only available if the "Display Local Namespace" option is on. The text box
- contains the prefix to use for local XREFs.
+
+ Sort References by - Allows the references to be sorted by Address or by type.
+ This is most useful when Group by Function is off.
+
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java
index e58c3d4a4c..6c800cf20f 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java
@@ -988,8 +988,8 @@ public class CodeBrowserPlugin extends Plugin
return; // not sure if this can happen
}
- Set refs = XReferenceUtil.getAllXrefs(location);
- XReferenceUtil.showAllXrefs(connectedProvider, tool, service, location, refs);
+ Set refs = XReferenceUtils.getAllXrefs(location);
+ XReferenceUtils.showXrefs(connectedProvider, tool, service, location, refs);
}
private GhidraProgramTableModel 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.
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/XReferenceUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/XReferenceUtil.java
index b02d7b2d6b..bc1d5294c4 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/XReferenceUtil.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/XReferenceUtil.java
@@ -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];
@@ -59,11 +61,11 @@ public class XReferenceUtil {
/**
* Returns an array containing the first maxNumber
* direct xref addresses to the specified code unit.
- *
+ *
* @param cu the code unit to generate the xrefs
* @param maxNumber max number of xrefs to get,
* or -1 to get all references
- *
+ *
* @return array first maxNumber
xrefs to the code unit
*/
public final static Address[] getXRefList(CodeUnit cu, int maxNumber) {
@@ -71,9 +73,8 @@ public class XReferenceUtil {
if (prog == null) {
return EMPTY_ADDR_ARRAY;
}
- List xrefList = new ArrayList();
- //lookup the direct xrefs to the current code unit
- //
+ List 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();
@@ -91,11 +92,11 @@ public class XReferenceUtil {
/**
* Returns an array containing the first maxNumber
* direct xref references to the specified code unit.
- *
+ *
* @param cu the code unit to generate the xrefs
* @param maxNumber max number of xrefs to get,
* or -1 to get all references
- *
+ *
* @return array first maxNumber
xrefs to the code unit
*/
public final static Reference[] getXReferences(CodeUnit cu, int maxNumber) {
@@ -103,7 +104,7 @@ public class XReferenceUtil {
if (prog == null) {
return EMPTY_REF_ARRAY;
}
- List xrefList = new ArrayList();
+ List 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 offcutList = new ArrayList();
+ List 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 offcutList = new ArrayList();
+ List 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;
@@ -300,7 +302,7 @@ public class XReferenceUtil {
}
/**
- * Shows all xrefs to the given location in a new table. These xrefs are retrieved
+ * Shows all xrefs to the given location in a new table. These xrefs are retrieved
* from the given supplier. Thus, it is up to the client to determine which xrefs to show.
*
* @param navigatable the navigatable used for navigation from the table
@@ -322,7 +324,7 @@ public class XReferenceUtil {
/**
* 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
+ * 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
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/XReferenceUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/XReferenceUtils.java
new file mode 100644
index 0000000000..bbfb99207c
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/XReferenceUtils.java
@@ -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 max
+ * 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 max
xrefs to the code unit
+ */
+ public final static List getXReferences(CodeUnit cu, int max) {
+ Program program = cu.getProgram();
+ if (program == null) {
+ Collections.emptyList();
+ }
+
+ // lookup the direct xrefs to the current code unit
+ List 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 getOffcutXReferences(CodeUnit cu, int max) {
+ Program program = cu.getProgram();
+ if (program == null) {
+ return Collections.emptyList();
+ }
+
+ if (cu.getLength() <= 1) {
+ return Collections.emptyList();
+ }
+
+ List 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 xrefs,
+ List 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 xrefs,
+ List 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 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 xrefs = getXReferences(cu, ALL_REFS);
+ List offcuts = getOffcutXReferences(cu, ALL_REFS);
+
+ // Remove duplicates
+ Set 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 xrefs) {
+
+ ReferencesFromTableModel model =
+ new ReferencesFromTableModel(new ArrayList<>(xrefs), serviceProvider,
+ location.getProgram());
+ TableComponentProvider provider = service.showTable(
+ "XRefs to " + location.getAddress().toString(), "XRefs", model, "XRefs", navigatable);
+ provider.installRemoveItemsAction();
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ReferenceLineDispenser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ReferenceLineDispenser.java
index 7c5a0dac75..c44f3fe5fd 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ReferenceLineDispenser.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ReferenceLineDispenser.java
@@ -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,48 +34,46 @@ class ReferenceLineDispenser extends AbstractLineDispenser {
private Memory memory;
private ReferenceManager referenceManager;
- private List lines = new ArrayList();
+ private List lines = new ArrayList<>();
ReferenceLineDispenser() {
}
- ReferenceLineDispenser(boolean forwardRefs, CodeUnit cu, Program program, ProgramTextOptions options) {
- this.memory = program.getMemory();
+ ReferenceLineDispenser(boolean forwardRefs, CodeUnit cu, Program program,
+ ProgramTextOptions options) {
+ this.memory = program.getMemory();
this.referenceManager = program.getReferenceManager();
this.displayRefHeader = options.isShowReferenceHeaders();
this.prefix = options.getCommentPrefix();
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);
}
ReferenceLineDispenser(Variable var, Program program, ProgramTextOptions options) {
- this.memory = program.getMemory();
+ this.memory = program.getMemory();
this.referenceManager = program.getReferenceManager();
this.displayRefHeader = options.isShowReferenceHeaders();
this.header = "XREF";
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 xrefs = new ArrayList();
- List offcuts = new ArrayList();
- XReferenceUtil.getVariableRefs(var, xrefs, offcuts);
+ List xrefs = new ArrayList<>();
+ List 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 refs) {
- Address [] addrs = new Address[refs.size()];
- for (int i=0; i < addrs.length; i++) {
+ private Address[] extractFromAddr(List 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 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 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;
+ }
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/IndentField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/IndentField.java
index ad9d09c1b6..080c574dfa 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/IndentField.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/IndentField.java
@@ -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
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ListingTextField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ListingTextField.java
index 1ae6ee3b67..502041cf1b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ListingTextField.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ListingTextField.java
@@ -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());
@@ -67,7 +71,7 @@ public class ListingTextField implements ListingField, TextField {
}
/**
- * Displays the given text, word-wrapping as needed to avoid clipping (up to the max number of
+ * Displays the given text, word-wrapping as needed to avoid clipping (up to the max number of
* lines.)
* @param factory the field factory that generated this field
* @param proxy the object used to populate this field
@@ -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 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 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;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/NamespacePropertyEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/NamespacePropertyEditor.java
index dba11577c2..a94ad0f93c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/NamespacePropertyEditor.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/NamespacePropertyEditor.java
@@ -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,
@@ -94,7 +94,7 @@ public class NamespacePropertyEditor extends PropertyEditorSupport implements Cu
showLocalCheckBox.addItemListener(e -> {
boolean enabled = showLocalCheckBox.isSelected();
- // only enable the text field if we are showing namespaces AND we are
+ // only enable the text field if we are showing namespaces AND we are
// overriding the display value
localPrefixField.setEnabled(enabled && useLocalPrefixCheckBox.isSelected());
useLocalPrefixCheckBox.setEnabled(enabled);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseField.java
index e284a9f899..bdbd1069a4 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseField.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseField.java
@@ -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,15 +152,16 @@ 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)
+ // the bottom of the heightAbove)
int toggleHandleStartY = -((heightAbove / 2) + (toggleHandleSize / 2));
int toggleHandleStartX = startX + (indentLevel * fieldWidth) + insetSpace;
// TODO: If we're in printing mode, trying to render these open/close images
- // causes the JVM to bomb. We'd like to eventually figure out why but in
+ // causes the JVM to bomb. We'd like to eventually figure out why but in
// the meantime we can safely comment this out and still generate an acceptable
// image.
//
@@ -178,7 +178,7 @@ public class OpenCloseField implements ListingField {
g.setColor(Color.LIGHT_GRAY);
- // draw the vertical lines to the left of the toggle handle (these are shown when
+ // draw the vertical lines to the left of the toggle handle (these are shown when
// there are vertical bars drawn for inset data)
int fieldTopY = -heightAbove;
int fieldBottomY = heightBelow;
@@ -205,7 +205,7 @@ public class OpenCloseField implements ListingField {
boolean lastAndClosed = isLast && !isOpen;
if (!lastAndClosed) {
- // extended vertical line below toggle handle
+ // extended vertical line below toggle handle
int buttonBottomY = toggleHandleStartY + toggleHandleSize;
g.drawLine(midpointX, buttonBottomY, midpointX, fieldBottomY);
}
@@ -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
@@ -332,7 +337,7 @@ public class OpenCloseField implements ListingField {
//==================================================================================================
// Static Methods
-//==================================================================================================
+//==================================================================================================
static int getOpenCloseHandleSize() {
return openImage.getIconWidth();
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java
index f28bd38910..04c478d956 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java
@@ -141,26 +141,23 @@ public class PlateFieldFactory extends FieldFactory {
}
CodeUnit cu = (CodeUnit) proxy.getObject();
- List elementList = new ArrayList<>(10);
+ List 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 textElements, PlateFieldFactory factory,
ProxyObj> proxy, int startX, int width, String commentText,
boolean isCommentClipped) {
super(textElements, startX, width, Integer.MAX_VALUE,
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableXRefFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableXRefFieldFactory.java
index ba9bb47dac..0bfea408ec 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableXRefFieldFactory.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableXRefFieldFactory.java
@@ -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
- *
- *
*/
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 xrefs = new ArrayList<>();
List 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 xrefs = new ArrayList<>();
List 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);
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableXRefFieldMouseHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableXRefFieldMouseHandler.java
index 9b2252a9f0..52d121c1a5 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableXRefFieldMouseHandler.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableXRefFieldMouseHandler.java
@@ -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 refs = XReferenceUtil.getVariableRefs(variable);
- XReferenceUtil.showAllXrefs(navigatable, serviceProvider, service, location, refs);
+ Set refs = getVariableRefs(variable);
+ XReferenceUtils.showXrefs(navigatable, serviceProvider, service, location, refs);
+ }
+
+ private Set getVariableRefs(Variable var) {
+
+ Set 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;
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java
index a2fbdee7be..e8f3022c7d 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldFactory.java
@@ -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 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(
- new HelpLocation("CodeBrowserPlugin", "XREFs_Field"));
+ 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 xrefs = XReferenceUtils.getXReferences(cu, maxXRefs + 1);
+ int maxOffcuts = Math.max(0, maxXRefs - xrefs.size());
+ List 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 xrefs, List 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 noFunction = new ArrayList<>();
+ TreeMap> 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 offcutSet = new HashSet<>(offcuts);
+ Predicate isOffcut = r -> offcutSet.contains(r);
+ HighlightFactory hlFactory =
+ new FieldHighlightFactory(hlProvider, getClass(), proxy.getObject());
+ Function currentFunction = functionManager.getFunctionContaining(cu.getMinAddress());
+ List 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 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 createXrefRowsByFunction(Program program, Function currentFunction,
+ TreeMap> xrefsByFunction, Predicate isOffcut,
+ int varWidth,
+ HighlightFactory hlFactory) {
+
+ FontMetrics metrics = getMetrics();
+ AttributedString delimiter = new AttributedString(delim, Color.BLACK, metrics);
+
+ int row = 0;
+ List elements = new ArrayList<>();
+ Set>> entries = xrefsByFunction.entrySet();
+ for (Entry> entry : entries) {
+
+ //
+ // Example row: functionName: 1234(c), 1238(c)
+ //
+
+ List 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 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 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 xrefs,
+ Function currentFunction, Predicate isOffcut, int availableLines,
+ HighlightFactory hlFactory) {
+
+ FontMetrics metrics = getMetrics();
+ AttributedString delimiter = new AttributedString(delim, Color.BLACK, metrics);
+ int row = startRow;
+ List 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 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 xrefs,
+ List 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 offcutSet = new HashSet<>(offcuts);
+ Predicate isOffcut = r -> offcutSet.contains(r);
+
+ boolean tooMany = totalXrefs > maxXRefs;
+ List 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 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 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 toFieldElements(List list, boolean showEllipses) {
+
+ List 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;
+ }
+
+ 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 = refFunction.equals(currentFunction);
+ 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;
}
+ CodeUnit cu = (CodeUnit) obj;
+
+ int[] cpath = null;
+ if (cu instanceof Data) {
+ cpath = ((Data) cu).getComponentPath();
+ }
+
+ XrefListingField field = (XrefListingField) listingField;
+ FieldElement element = field.getFieldElement(row, col);
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);
- }
-
- 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,
+ 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);
+ }
+
+ }
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldMouseHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldMouseHandler.java
index 96d0e78613..5f4b52f883 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldMouseHandler.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldMouseHandler.java
@@ -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;
@@ -51,7 +51,7 @@ public class XRefFieldMouseHandler implements FieldMouseHandlerExtension {
return false;
}
- // If I double-click on the XRef Header, show references to this place, also works on
+ // If I double-click on the XRef Header, show references to this place, also works on
// 'more' field. This is much nicer if you have multiple references to navigate.
if (isXREFHeaderLocation(location)) {
showXRefDialog(sourceNavigatable, location, serviceProvider);
@@ -105,8 +105,8 @@ public class XRefFieldMouseHandler implements FieldMouseHandlerExtension {
return;
}
- Set refs = XReferenceUtil.getAllXrefs(location);
- XReferenceUtil.showAllXrefs(navigatable, serviceProvider, service, location, refs);
+ Set refs = XReferenceUtils.getAllXrefs(location);
+ XReferenceUtils.showXrefs(navigatable, serviceProvider, service, location, refs);
}
protected ProgramLocation getReferredToLocation(Navigatable sourceNavigatable,
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefHeaderFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefHeaderFieldFactory.java
index 4d587296b8..1c2d6140b9 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefHeaderFieldFactory.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefHeaderFieldFactory.java
@@ -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 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 = "+";
+ }
+ return "XREF[" + xrefCount + "," + offcutCount + modifier + "]: ";
}
- if (xrefCnt > 0) {
- return "XREF[" + xrefCnt + "]: ";
+
+ if (xrefCount > 0) {
+ return "XREF[" + xrefCount + "]: ";
}
return null;
}
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/EolCommentFieldFactoryTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/EolCommentFieldFactoryTest.java
index 5f382d96d5..1a9b61d77d 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/EolCommentFieldFactoryTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/EolCommentFieldFactoryTest.java
@@ -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);
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/XRefFieldFactoryTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/XRefFieldFactoryTest.java
new file mode 100644
index 0000000000..97d6b7b97f
--- /dev/null
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/XRefFieldFactoryTest.java
@@ -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 rowElements = getRowElements(tf, row);
+ return StringUtils.join(rowElements);
+ }
+
+ private List getRowElements(ListingTextField tf, int row) {
+
+ List 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();
+ }
+
+}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangTextField.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangTextField.java
index cf04541e90..bacb407a59 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangTextField.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangTextField.java
@@ -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);
}
/**
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java
index c7d7f09ff3..015dcaa50e 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerClipboardProvider.java
@@ -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 COPY_TYPES = new LinkedList();
+ private static final List 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 listeners = new CopyOnWriteArraySet();
+ private Set 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;
@@ -249,7 +249,7 @@ public class DecompilerClipboardProvider extends ByteCopier
//==================================================================================================
// Unsupported Operations
-//==================================================================================================
+//==================================================================================================
@Override
public boolean enablePaste() {
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/AbstractTextFieldElement.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/AbstractTextFieldElement.java
index e81cd91f5f..2969e64b4a 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/AbstractTextFieldElement.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/AbstractTextFieldElement.java
@@ -24,9 +24,9 @@ import docking.widgets.fieldpanel.support.RowColLocation;
/**
* An object that wraps a string and provides data that describes how to render
- * that string.
+ * that string.
*
- * This class was created as a place to house attributes of rendering that
+ * This class was created as a place to house attributes of rendering that
* are not described by Java's Font object, like underlining.
*
*
@@ -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);
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/AttributedString.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/AttributedString.java
index a6f3ccbfed..777741dc67 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/AttributedString.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/AttributedString.java
@@ -24,9 +24,9 @@ import docking.util.GraphicsUtils;
/**
* An object that wraps a string and provides data that describes how to render
- * that string.
+ * that string.
*
- * This class was created as a place to house attributes of rendering that
+ * This class was created as a place to house attributes of rendering that
* are not described by Java's Font object, like underlining.
*
*
@@ -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;
}
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ClippingTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ClippingTextField.java
index 961048623b..b18e84ff03 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ClippingTextField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ClippingTextField.java
@@ -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);
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeAttributedString.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeAttributedString.java
index 66e1f45f1a..90760c600d 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeAttributedString.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeAttributedString.java
@@ -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();
}
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeFieldElement.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeFieldElement.java
index fbe60d9051..38c56c63a1 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeFieldElement.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeFieldElement.java
@@ -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++) {
@@ -114,7 +109,7 @@ public class CompositeFieldElement implements FieldElement {
//==================================================================================================
// FontMetrics methods
-//==================================================================================================
+//==================================================================================================
@Override
public int getStringWidth() {
@@ -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());
}
@@ -141,7 +136,7 @@ public class CompositeFieldElement implements FieldElement {
//==================================================================================================
// Paint methods
-//==================================================================================================
+//==================================================================================================
@Override
public void paint(JComponent c, Graphics g, int x, int y) {
@@ -217,9 +212,14 @@ public class CompositeFieldElement implements FieldElement {
return getText().length();
}
+ @Override
+ public String toString() {
+ return getText();
+ }
+
//==================================================================================================
// Location Info
-//==================================================================================================
+//==================================================================================================
@Override
public RowColLocation getDataLocationForCharacterIndex(int characterIndex) {
@@ -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;
}
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextField.java
new file mode 100644
index 0000000000..9bdd9ba668
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextField.java
@@ -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.
+ *
+ *
This class allows clients to create custom text layout behavior by combining individual
+ * TextFields that dictate layout behavior. As an example, consider this rendering:
+ *
+ * 1) This is some text...
+ * 2) This
+ * is
+ * more
+ * text
+ *
+ * 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 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 lines;
+
+ // used in the getText() method to separate rows without adding newlines
+ private String rowSeparator;
+
+ private boolean isClipped;
+
+ public CompositeVerticalLayoutTextField(List fields, int startX, int width,
+ int maxLines, HighlightFactory hlFactory) {
+ this(fields, startX, width, maxLines, hlFactory, " ");
+ }
+
+ protected CompositeVerticalLayoutTextField(List 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 generateLines(List fields) {
+
+ List list = new ArrayList<>();
+ for (TextField field : fields) {
+ list.add(field.getTextWithLineSeparators());
+ }
+ return list;
+ }
+
+ private String generateText(List fields, String delimiter) {
+
+ StringBuilder buf = new StringBuilder();
+ for (TextField element : fields) {
+ buf.append(element.getText()).append(delimiter);
+ }
+ return buf.toString();
+ }
+
+ private List layoutRows(List fields, int maxLines) {
+
+ List 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 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 getAllRows(int maxRow) {
+ int currentRow = 0;
+ List list = new ArrayList<>();
+ for (FieldRow row : fieldRows) {
+ if (currentRow > maxRow) {
+ break;
+ }
+
+ list.add(row);
+ currentRow += row.field.getNumRows();
+ }
+ return list;
+ }
+
+ // for testing
+ protected List 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 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 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 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);
+ }
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/EmptyTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/EmptyTextField.java
index 9d2f2ff651..0cbd444a36 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/EmptyTextField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/EmptyTextField.java
@@ -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
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/Field.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/Field.java
index 3715a0ac16..02fa87386d 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/Field.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/Field.java
@@ -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 clip the clipping region to paint into
+ * @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);
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FieldElement.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FieldElement.java
index 70224eda7f..7b66d65eef 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FieldElement.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FieldElement.java
@@ -23,7 +23,7 @@ import javax.swing.JComponent;
import docking.widgets.fieldpanel.support.RowColLocation;
/**
- * Used by {@link Field}s to combine text, attributes and location information (for example to and
+ * Used by {@link Field}s to combine text, attributes and location information (for example to and
* from screen and data locations). FieldFactory classes can use the various implementations
* of this interface, or create new ones, to include additional information specific to the fields
* that they create.
@@ -37,14 +37,14 @@ public interface FieldElement {
public String getText();
/**
- * Returns the length of the text within this element. This is a convenience method for
+ * Returns the length of the text within this element. This is a convenience method for
* calling getText().length()
.
* @return the length of the text within this element.
*/
public int length();
/**
- * Returns the string width of this element. The width is based upon the associated
+ * Returns the string width of this element. The width is based upon the associated
* FontMetrics object within this element.
* @return the string width of this element.
*/
@@ -87,7 +87,7 @@ public interface FieldElement {
public FieldElement substring(int start);
/**
- * Returns a new FieldElement containing just the characters beginning at the given start
+ * Returns a new FieldElement containing just the characters beginning at the given start
* index (inclusive) and ending at the given end index (exclusive).
*
* @param start The starting index (inclusive) from which to substring this element.
@@ -97,11 +97,11 @@ public interface FieldElement {
public FieldElement substring(int start, int end);
/**
- * Returns a new FieldElement with all occurrences of the target characters replaced with the
+ * Returns a new FieldElement with all occurrences of the target characters replaced with the
* given replacement character.
* @param targets The array of characters to replace.
* @param replacement The replacement character.
- * @return a new FieldElement with all occurrences of the target characters replaced with the
+ * @return a new FieldElement with all occurrences of the target characters replaced with the
* given replacement character.
*/
public FieldElement replaceAll(char[] targets, char replacement);
@@ -111,13 +111,13 @@ public interface FieldElement {
* element that will fit within the given width.
*
* @param width The width constraint
- * @return the maximum number of characters from this field element that will fit within
+ * @return the maximum number of characters from this field element that will fit within
* the given width.
*/
public int getMaxCharactersForWidth(int width);
/**
- * Translates the given character index to a data location related to the data model, as
+ * Translates the given character index to a data location related to the data model, as
* determined by the FieldFactory.
*
* @param characterIndex The character index to translate.
@@ -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);
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FlowLayoutTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FlowLayoutTextField.java
index 762d9ea2c5..6b6d992fa9 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FlowLayoutTextField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/FlowLayoutTextField.java
@@ -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 metrics.stringWidth(text) > width
, then the text
- * will be clipped. No wrapping will be performed. If text
- * 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
+ * metrics.stringWidth(text) > width
, then the text will be wrapped.
+ * If text
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 subFields = new ArrayList();
+ /**
+ * This constructor will create a text field that will render one line of text. If
+ * metrics.stringWidth(text) > width
, then the text will be wrapped.
+ * If text
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 elements, int startX,
+ int width, int maxLines, HighlightFactory hlFactory) {
+ super(createLineElements(elements, width), startX, width, maxLines, hlFactory, "");
+ }
+ private static List createLineElements(List elements,
+ int width) {
+ List 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 elements, int from,
+ int length) {
+ return new CompositeFieldElement(elements.subList(from, from + length));
+ }
+
+ private static int getNumberOfElementsPerLine(List 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;
}
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ReverseClippingTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ReverseClippingTextField.java
index 64d84cf8a7..7fe81e8e7b 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ReverseClippingTextField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/ReverseClippingTextField.java
@@ -97,7 +97,7 @@ public class ReverseClippingTextField implements TextField {
}
isClipped = true;
- // get the index of the start character that will fit
+ // get the index of the start character that will fit
startingCharIndex =
textElement.getMaxCharactersForWidth(w - (availableWidth - DOT_DOT_DOT_WIDTH)) + 1;
startingCharIndex = Math.min(startingCharIndex, textElement.length());
@@ -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));
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/SimpleImageField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/SimpleImageField.java
index f2f34a5aea..6db617874d 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/SimpleImageField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/SimpleImageField.java
@@ -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
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/SimpleTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/SimpleTextField.java
index 7a3f3fb7fa..d86bbff75d 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/SimpleTextField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/SimpleTextField.java
@@ -110,10 +110,15 @@ public class SimpleTextField implements Field {
return startX;
}
+ @Override
+ public int getNumDataRows() {
+ return 1;
+ }
+
/**
- *
- * @see docking.widgets.fieldpanel.field.Field#getNumRows()
- */
+ *
+ * @see docking.widgets.fieldpanel.field.Field#getNumRows()
+ */
@Override
public int getNumRows() {
return 1;
@@ -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());
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/EmptyFieldElement.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/StrutFieldElement.java
similarity index 72%
rename from Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/EmptyFieldElement.java
rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/StrutFieldElement.java
index 702ac62c15..c74069d6ae 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/EmptyFieldElement.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/StrutFieldElement.java
@@ -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
+ }
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/TextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/TextField.java
index b4735e9177..9a8789d7f1 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/TextField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/TextField.java
@@ -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,37 +15,40 @@
*/
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);
-
- /**
- * Translates a screen coordinate to a row and column in the data from the factory
- * @param screenRow the row in the displayed field text.
- * @param screenColumn the column in the displayed field text.
- * @return a RowColLocation containing the row and column within the data from the factory.
- */
- public RowColLocation screenToDataLocation(int screenRow, int screenColumn);
-
- /**
- * 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.
- */
- public RowColLocation dataToScreenLocation(int dataRow, int dataColumn);
- /**
- * Returns true if the field is not displaying all the text information
- */
+ /**
+ * Translates a screen coordinate to a row and column in the data from the factory
+ * @param screenRow the row in the displayed field text.
+ * @param screenColumn the column in the displayed field text.
+ * @return a RowColLocation containing the row and column within the data from the factory.
+ */
+ public RowColLocation screenToDataLocation(int screenRow, int screenColumn);
+
+ /**
+ * 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; 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();
-
+
/**
* Returns the FieldElement at the given screen location.
* @param screenRow the row on the screen
@@ -54,5 +56,5 @@ public interface TextField extends Field {
* @return the FieldElement at the given screen location.
*/
public FieldElement getFieldElement(int screenRow, int screenColumn);
-
+
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/TextFieldElement.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/TextFieldElement.java
index 6bbd3ac26f..beaed3377e 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/TextFieldElement.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/TextFieldElement.java
@@ -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);
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/VerticalLayoutTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/VerticalLayoutTextField.java
index db4ff1b168..80b3b6ade0 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/VerticalLayoutTextField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/VerticalLayoutTextField.java
@@ -16,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 subFields; // list of fields for FieldElements
+ protected List 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 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 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 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 generateLines(List textElements) {
+
+ List list = new ArrayList<>();
+ for (FieldElement field : textElements) {
+ list.add(field.getText());
+ }
+ return list;
+ }
+
+ private String generateText(List 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 getSubfields() {
return Collections.unmodifiableList(subFields);
@@ -372,27 +419,29 @@ public class VerticalLayoutTextField implements TextField {
@Override
public void rowHeightChanged(int heightAbove1, int heightBelow) {
- // most fields don't care
+ // most fields don't care
}
@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 layoutElements(int maxLines) {
- List newSubFields = new ArrayList<>();
+ protected List layoutElements(List textElements, int maxLines) {
+ List 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);
@@ -406,83 +455,70 @@ public class VerticalLayoutTextField implements TextField {
}
/**
- * Translates the row and column to a String index and character offset into
+ * Translates the row and column to a String index and character offset into
* that string.
* @param screenRow the row containing the location.
* @param screenColumn the character position in the row of the location
- * @return a MultiStringLocation containing the string index and position
+ * @return a MultiStringLocation containing the string index and position
* within that string.
*/
@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
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/WrappingVerticalLayoutTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/WrappingVerticalLayoutTextField.java
index 2244301df5..2fe0578fab 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/WrappingVerticalLayoutTextField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/WrappingVerticalLayoutTextField.java
@@ -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
+ * 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);
}
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/DefaultRowColLocation.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/DefaultRowColLocation.java
new file mode 100644
index 0000000000..bd3ec096a2
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/DefaultRowColLocation.java
@@ -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);
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldSelectionHelper.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldSelectionHelper.java
index e07974df5a..022891dbd2 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldSelectionHelper.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldSelectionHelper.java
@@ -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.
@@ -35,9 +34,12 @@ 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());
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java
index 5e0ddbab88..8a3130dc7b 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldUtils.java
@@ -15,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;
@@ -42,14 +41,14 @@ public class FieldUtils {
* Splits the given FieldElement into sub-elements by wrapping the element on whitespace.
*
* @param fieldElement The element to wrap
- * @param width The maximum width to allow before wrapping
+ * @param width The maximum width to allow before wrapping
* @return The wrapped elements
*/
- public static FieldElement[] wrap(FieldElement fieldElement, int width) {
+ public static List wrap(FieldElement fieldElement, int width) {
FieldElement originalFieldElement = fieldElement.replaceAll(WHITE_SPACE, ' ');
if (originalFieldElement.getStringWidth() <= width) {
- return new FieldElement[] { originalFieldElement };
+ return Arrays.asList(originalFieldElement);
}
List 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 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 lines = new ArrayList<>();
@@ -99,14 +98,14 @@ public class FieldUtils {
}
}
lines.add(originalFieldElement);
- return lines.toArray(new FieldElement[lines.size()]);
+ return lines;
}
/**
* Splits the given FieldElement into sub-elements by wrapping the element on whitespace.
*
* @param fieldElement The element to wrap
- * @param width The maximum width to allow before wrapping
+ * @param width The maximum width to allow before wrapping
* @return The wrapped elements
*/
public static List wordWrapList(FieldElement fieldElement, int width) {
@@ -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,30 +156,19 @@ 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
+ // in this case just take all valid characters and then exit
for (int index = 1; index < buffer.length(); index++) {
int charAt = buffer.charAt(index);
if (!Character.isJavaIdentifierPart(charAt)) {
@@ -189,7 +178,7 @@ public class FieldUtils {
return buffer.toString();
}
- // the following case is when the given string is surrounded by "goofy" characters
+ // the following case is when the given string is surrounded by "goofy" characters
int index = 0;
int charAt = buffer.charAt(index);
while (!Character.isJavaIdentifierPart(charAt) && buffer.length() > 0) {
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/RowColLocation.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/RowColLocation.java
index 4cb0def1d8..6821b56458 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/RowColLocation.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/RowColLocation.java
@@ -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 ) {
- RowColLocation loc = (RowColLocation) object;
- return (row == loc.row) && (col == loc.col);
+
+ if (!getClass().equals(object.getClass())) {
+ return false;
}
- return false;
+
+ RowColLocation loc = (RowColLocation) object;
+ return (row == loc.row) && (col == loc.col);
}
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/RowLayout.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/RowLayout.java
index f0890fcc08..0b8bf1916e 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/RowLayout.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/RowLayout.java
@@ -72,7 +72,7 @@ public class RowLayout implements Layout {
// Can only compress the last field, as the rest are potentially part of a grid surrounded
// by other layouts
//
- // Notes: we have to account for any offset for fields that are disabled and are in
+ // Notes: we have to account for any offset for fields that are disabled and are in
// the beginning of the row.
//
int startX = fields[0].getStartX();
@@ -180,8 +180,9 @@ public class RowLayout implements Layout {
gapIndex = fields.length;
}
int startX =
- gapIndex == 0 ? rect.x : fields[gapIndex - 1].getStartX() +
- fields[gapIndex - 1].getWidth();
+ gapIndex == 0 ? rect.x
+ : fields[gapIndex - 1].getStartX() +
+ fields[gapIndex - 1].getWidth();
int endX = gapIndex >= fields.length ? rect.x + rect.width : fields[gapIndex].getStartX();
if (startX < endX) {
@@ -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);
}
diff --git a/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/FlowLayoutTextFieldTest.java b/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/FlowLayoutTextFieldTest.java
index 604bd46201..31682df7e7 100644
--- a/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/FlowLayoutTextFieldTest.java
+++ b/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/FlowLayoutTextFieldTest.java
@@ -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 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() {
-
- }
-
}
diff --git a/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/VerticalLayoutTextFieldTest.java b/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/VerticalLayoutTextFieldTest.java
index dacf10ab15..4a88f74358 100644
--- a/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/VerticalLayoutTextFieldTest.java
+++ b/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/VerticalLayoutTextFieldTest.java
@@ -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 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));
}
}
diff --git a/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextFieldTest.java b/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextFieldTest.java
new file mode 100644
index 0000000000..4f9f8c51b8
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/test/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextFieldTest.java
@@ -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 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 lines) {
+
+ rows = lines;
+
+ List elements = new ArrayList<>();
+ int row = 0;
+ for (String line : lines) {
+ elements.add(createRow(row++, line, Color.BLUE));
+ }
+
+ List 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 lines) {
+
+ rows = lines;
+
+ List elements = new ArrayList<>();
+ int row = 0;
+ for (String line : lines) {
+ elements.add(createRow(row++, line, Color.BLUE));
+ }
+
+ List 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 lines) {
+
+ rows = lines;
+
+ int row = 0;
+ List 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 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());
+ }
+}
diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java
index e069eabb60..de7beb67e8 100644
--- a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java
+++ b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java
@@ -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.*;
@@ -219,7 +220,7 @@ public abstract class AbstractGenericTest extends AbstractGTest {
/**
* A callback for subclasses when a test has failed. This will be called
- * after tearDown()
. This means that any diagnostics will have to
+ * after tearDown()
. This means that any diagnostics will have to
* take into account items that have already been disposed.
*
* @param e the exception that happened when the test failed
@@ -1118,7 +1119,7 @@ public abstract class AbstractGenericTest extends AbstractGTest {
}
/**
- * Call this version of {@link #runSwing(Runnable)} when you expect your runnable may
+ * Call this version of {@link #runSwing(Runnable)} when you expect your runnable may
* throw exceptions
*
* @param callback the runnable code snippet to call
@@ -1148,13 +1149,13 @@ public abstract class AbstractGenericTest extends AbstractGTest {
public static void runSwing(Runnable runnable, boolean wait) {
- //
+ //
// Special Case: this check handled re-entrant test code. That is, an calls to runSwing()
// that are made from within a runSwing() call. Most clients do not do
// this, but it can happen when a client makes a test API call (which itself
- // calls runSwing()) from within a runSwing() call.
- //
- // Calling the run method directly here ensures that the order of client
+ // calls runSwing()) from within a runSwing() call.
+ //
+ // Calling the run method directly here ensures that the order of client
// requests is preserved.
//
if (SwingUtilities.isEventDispatchThread()) {
@@ -1167,7 +1168,7 @@ public abstract class AbstractGenericTest extends AbstractGTest {
return;
}
- // don't wait; invoke later; catch any exceptions ourselves in order to fail-fast
+ // don't wait; invoke later; catch any exceptions ourselves in order to fail-fast
Runnable swingExceptionCatcher = () -> {
try {
runnable.run();
@@ -1243,11 +1244,11 @@ public abstract class AbstractGenericTest extends AbstractGTest {
doRun(swingExceptionCatcher);
}
catch (InterruptedException | InvocationTargetException e) {
- // Assume that if we have an exception reported by our catcher, then that is
+ // Assume that if we have an exception reported by our catcher, then that is
// the root cause of this exception and do not report this one. The typical
// exception here is an InterrruptedException that is caused by our test
- // harness when it is interrupting the test thread after a previous Swing
- // exception that we have detected--we don't care to report the
+ // harness when it is interrupting the test thread after a previous Swing
+ // exception that we have detected--we don't care to report the
// InterruptedException, as we caused it. The InvocationTargetException should
// be handled by our runnable above.
}
@@ -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
@@ -1582,7 +1596,7 @@ public abstract class AbstractGenericTest extends AbstractGTest {
//==================================================================================================
// Swing Methods
-//==================================================================================================
+//==================================================================================================
/**
* Waits for the Swing thread to process any pending events. This method
@@ -1685,11 +1699,11 @@ public abstract class AbstractGenericTest extends AbstractGTest {
// calls all execute in the Swing thread in a blocking fashion, so when we are done
// flushing, there should be no more work scheduled due to us flushing. Due to other
// potential background threads though, more work may be scheduled as we are working.
- // Thus, for fast tests, you should not have background work happening that is not
+ // Thus, for fast tests, you should not have background work happening that is not
// directly related to your code being tested.
//
- // arbitrary; we have at least one level of a manager triggering another manager,
+ // arbitrary; we have at least one level of a manager triggering another manager,
// which would be 2
int n = 3;
for (int i = 0; i < n; i++) {
@@ -1743,8 +1757,8 @@ public abstract class AbstractGenericTest extends AbstractGTest {
SwingUtilities.invokeAndWait(empty);
}
catch (Exception e) {
- // Assumption: since our runnable is empty, this can only an interrupted
- // exception, which can happen if our test framework decides to
+ // Assumption: since our runnable is empty, this can only an interrupted
+ // exception, which can happen if our test framework decides to
// shut the operation down.
return;
}