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 1358606747..add67b32f2 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 @@ -385,7 +385,7 @@ public class XRefFieldFactory extends FieldFactory { int availableLines = maxLines - functionRows.size(); if (tooMany) { // save room for the "more" field at the end - availableLines -= 1; + availableLines = Math.max(1, availableLines--); } // @@ -394,7 +394,7 @@ public class XRefFieldFactory extends FieldFactory { // // 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 + // Our 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 = @@ -802,10 +802,14 @@ public class XRefFieldFactory extends FieldFactory { RowColLocation loc = field.screenToDataLocation(row, col); if (element instanceof XrefFieldElement) { XrefFieldElement xrefElement = (XrefFieldElement) element; - Reference xref = xrefElement.getXref(); - Address refAddr = xref.getFromAddress(); - return new XRefFieldLocation(cu.getProgram(), cu.getMinAddress(), cpath, refAddr, row, - loc.col()); + return getXRefLocation(xrefElement, cu, cpath, loc, row); + } + else if (element instanceof StrutFieldElement) { + FieldElement baseElement = ((StrutFieldElement) element).getBaseType(); + if (baseElement instanceof XrefFieldElement) { + XrefFieldElement xrefElement = (XrefFieldElement) baseElement; + return getXRefLocation(xrefElement, cu, cpath, loc, row); + } } String text = element.getText(); @@ -817,6 +821,15 @@ public class XRefFieldFactory extends FieldFactory { return null; } + private XRefFieldLocation getXRefLocation(XrefFieldElement xrefElement, CodeUnit cu, + int[] cpath, + RowColLocation loc, int row) { + Reference xref = xrefElement.getXref(); + Address refAddr = xref.getFromAddress(); + return new XRefFieldLocation(cu.getProgram(), cu.getMinAddress(), cpath, refAddr, row, + loc.col()); + } + protected String getBlockName(Program pgm, Address addr) { Memory mem = pgm.getMemory(); MemoryBlock block = mem.getBlock(addr); 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 ff81d2e2ec..47b2f9ea7d 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 @@ -18,8 +18,7 @@ package ghidra.app.util.viewer.field; import java.awt.event.MouseEvent; import java.util.Set; -import docking.widgets.fieldpanel.field.FieldElement; -import docking.widgets.fieldpanel.field.TextField; +import docking.widgets.fieldpanel.field.*; import ghidra.app.nav.Navigatable; import ghidra.app.services.GoToService; import ghidra.app.util.XReferenceUtils; @@ -58,8 +57,7 @@ public class XRefFieldMouseHandler implements FieldMouseHandlerExtension { } Address referencedAddress = getFromReferenceAddress(location); - String clickedText = getText(clickedObject); - boolean isInvisibleXRef = XRefFieldFactory.MORE_XREFS_STRING.equals(clickedText); + boolean isInvisibleXRef = isInvisibleXRef(clickedObject); if (isInvisibleXRef) { showXRefDialog(sourceNavigatable, location, serviceProvider); return true; @@ -68,6 +66,21 @@ public class XRefFieldMouseHandler implements FieldMouseHandlerExtension { return goTo(sourceNavigatable, referencedAddress, goToService); } + private boolean isInvisibleXRef(Object clickedObject) { + + String clickedText = getText(clickedObject); + if (XRefFieldFactory.MORE_XREFS_STRING.equals(clickedText)) { + return true; + } + + if (clickedObject instanceof StrutFieldElement) { + // this implies that the xrefs field has been clipped and has used a struct to trigger + // clipping + return true; + } + return false; + } + protected boolean isXREFHeaderLocation(ProgramLocation location) { return location instanceof XRefHeaderFieldLocation; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentPlaceholder.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentPlaceholder.java index 9008c949ae..9d4fcafc7d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentPlaceholder.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentPlaceholder.java @@ -35,6 +35,12 @@ import utilities.util.reflection.ReflectionUtilities; * Class to hold information about a dockable component with respect to its position within the * windowing system. It also holds identification information about the provider so that its * location can be reused when the provider is re-opened. + *

+ * The placeholder will be used to link previously saved position information. The tool will + * initially construct plugins and their component providers with default position information. + * Then, any existing xml data will be restored, which may have provider position information. + * The restoring of the xml will create placeholders with this saved information. Finally, the + * restored placeholders will be linked with existing component providers. */ public class ComponentPlaceholder { private String name; @@ -68,7 +74,7 @@ public class ComponentPlaceholder { * @param name the name of the component * @param owner the owner of the component * @param group the window group - * @param title the title + * @param title the title * @param show whether or not the component is showing * @param node componentNode that has this placeholder * @param instanceID the instance ID @@ -116,7 +122,7 @@ public class ComponentPlaceholder { if (node != null && disposed) { // // TODO Hack Alert! (When this is removed, also update ComponentNode) - // + // // This should not happen! We have seen this bug recently Msg.debug(this, "Found disposed component that was not removed from the hierarchy " + "list: " + this, ReflectionUtilities.createJavaFilteredThrowable()); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DetachedWindowNode.java b/Ghidra/Framework/Docking/src/main/java/docking/DetachedWindowNode.java index 3d655a03a2..e15055d5cd 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DetachedWindowNode.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DetachedWindowNode.java @@ -242,13 +242,13 @@ class DetachedWindowNode extends WindowNode { /** * Creates a list of titles from the given component providers and placeholders. The utility - * of this method is that it will group like component providers into one title value + * of this method is that it will group like component providers into one title value * instead of having one value for each placeholder. */ private List generateTitles(List placeholders) { // - // Decompose the given placeholders into a mapping of provider names to placeholders + // Decompose the given placeholders into a mapping of provider names to placeholders // that share that name. This lets us group placeholders that are multiple instances of // the same provider. // @@ -368,7 +368,7 @@ class DetachedWindowNode extends WindowNode { } /** - * Ensures the bounds of this window have a valid location and size + * Ensures the bounds of this window have a valid location and size */ private void adjustBounds() { @@ -474,6 +474,16 @@ class DetachedWindowNode extends WindowNode { @Override void dispose() { + disposeWindow(); + + if (child != null) { + child.parent = null; + child.dispose(); + child = null; + } + } + + private void disposeWindow() { if (dropTargetHandler != null) { dropTargetHandler.dispose(); } @@ -485,12 +495,24 @@ class DetachedWindowNode extends WindowNode { window.dispose(); window = null; } + } + /** + * An oddly named method for an odd use case. This method is meant to take an existing window, + * such as that created by default when loading plugins, and hide it. Clients cannot simply + * call dispose(), as that would also dispose the entire child hierarchy of this class. This + * method is intended to keep all children not disposed while allowing this window node to go + * away. + */ + void disconnect() { + + // note: do not call child.dispose() here if (child != null) { child.parent = null; - child.dispose(); child = null; } + + disposeWindow(); } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/PlaceholderManager.java b/Ghidra/Framework/Docking/src/main/java/docking/PlaceholderManager.java index 93d1a9c8e5..349bb3faa7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/PlaceholderManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/PlaceholderManager.java @@ -43,17 +43,22 @@ class PlaceholderManager { } ComponentPlaceholder replacePlaceholder(ComponentProvider provider, - ComponentPlaceholder oldPlaceholder) { + ComponentPlaceholder defaultPlaceholder) { - ComponentPlaceholder newPlaceholder = createOrRecyclePlaceholder(provider, oldPlaceholder); + // Note: the 'restoredPlaceholder' is from xml; the 'defaultPlaceholder' is that which was + // created by a plugin as it was constructed. If there is no placeholder in the xml, + // then the original 'defaultPlaceholder' will be returned from + // createOrRecyclePlaceholder(). + ComponentPlaceholder restoredPlaceholder = + createOrRecyclePlaceholder(provider, defaultPlaceholder); - moveActions(oldPlaceholder, newPlaceholder); - if (!oldPlaceholder.isHeaderShowing()) { - newPlaceholder.showHeader(false); + moveActions(defaultPlaceholder, restoredPlaceholder); + if (!defaultPlaceholder.isHeaderShowing()) { + restoredPlaceholder.showHeader(false); } - if (oldPlaceholder.isShowing() != newPlaceholder.isShowing()) { - if (newPlaceholder.isShowing()) { + if (defaultPlaceholder.isShowing() != restoredPlaceholder.isShowing()) { + if (restoredPlaceholder.isShowing()) { provider.componentShown(); } else { @@ -61,11 +66,13 @@ class PlaceholderManager { } } - if (newPlaceholder != oldPlaceholder) { - oldPlaceholder.dispose(); - removePlaceholder(oldPlaceholder); + // if we have found a replacement placeholder, then remove the default placeholder + if (restoredPlaceholder != defaultPlaceholder) { + defaultPlaceholder.dispose(); + removePlaceholder(defaultPlaceholder); } - return newPlaceholder; + + return restoredPlaceholder; } /** @@ -240,8 +247,8 @@ class PlaceholderManager { // 1) share the same owner (plugin), and // 2) are in the same group, or related groups // - // If there are not other providers that share the same owner as the one we are given, - // then we will search all providers. This allows different plugins to share + // If there are not other providers that share the same owner as the one we are given, + // then we will search all providers. This allows different plugins to share // window arrangement. // Set buddies = activePlaceholders; @@ -275,7 +282,7 @@ class PlaceholderManager { * provider. * @param activePlaceholders the set of currently showing placeholders. * @param newInfo the placeholder for the new provider to be shown. - * @return an existing matching placeholder or null. + * @return an existing matching placeholder or null. */ private ComponentPlaceholder findBestPlaceholderToStackUpon( Set activePlaceholders, ComponentPlaceholder newInfo) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/RootNode.java b/Ghidra/Framework/Docking/src/main/java/docking/RootNode.java index d77bebf246..422894f3e0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/RootNode.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/RootNode.java @@ -452,8 +452,13 @@ class RootNode extends WindowNode { /** * Restores the component hierarchy from the given XML JDOM element. + *

+ * The process of restoring from xml will create new {@link ComponentPlaceholder}s that will be + * used to replace any existing matching placeholders. This allows the already loaded default + * placeholders to be replaced by the previously saved configuration. * - * @param root the XML from which to restore the state. + * @param rootNodeElement the XML from which to restore the state. + * @return the newly created placeholders */ List restoreFromXML(Element rootNodeElement) { invalid = true; @@ -463,7 +468,7 @@ class RootNode extends WindowNode { detachedWindows.clear(); for (DetachedWindowNode windowNode : copy) { notifyWindowRemoved(windowNode); - windowNode.dispose(); + windowNode.disconnect(); } int x = Integer.parseInt(rootNodeElement.getAttributeValue("X_POS")); @@ -603,7 +608,7 @@ class RootNode extends WindowNode { //================================================================================================== // Inner Classes -//================================================================================================== +//================================================================================================== /** Interface to wrap JDialog and JFrame so that they can be used by one handle */ private interface SwingWindowWrapper { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextField.java index b28d4e97c9..58096b17c5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/CompositeVerticalLayoutTextField.java @@ -146,7 +146,7 @@ public class CompositeVerticalLayoutTextField implements TextField { FieldElement[] elements = new FieldElement[] { element, - new StrutFieldElement(500) + new StrutFieldElement(element, 500) }; FieldElement compositeElement = new CompositeFieldElement(elements); return new ClippingTextField(startX, width, compositeElement, hlFactory); @@ -284,14 +284,13 @@ public class CompositeVerticalLayoutTextField implements TextField { int startY = myStartY; int translatedY = 0; - for (int i = 0; i < fieldRows.size(); i++) { + for (FieldRow fieldRow : fieldRows) { // 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; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/StrutFieldElement.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/StrutFieldElement.java index c74069d6ae..9cc3ad8838 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/StrutFieldElement.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/field/StrutFieldElement.java @@ -28,12 +28,29 @@ import docking.widgets.fieldpanel.support.RowColLocation; */ public class StrutFieldElement implements FieldElement { + private final FieldElement baseElement; private final int width; - public StrutFieldElement(int width) { + /** + * Constructor. Clients may choose to pass + * + * @param baseElement the base type replaced by this strut; may be null if no type is being + * replaced + * @param width the width of this strut class + */ + public StrutFieldElement(FieldElement baseElement, int width) { + this.baseElement = baseElement; this.width = width; } + /** + * Returns the base type replaced by this strut; may be null + * @return the base type replaced by this strut; may be null + */ + public FieldElement getBaseType() { + return baseElement; + } + @Override public char charAt(int index) { return ' '; @@ -101,12 +118,12 @@ public class StrutFieldElement implements FieldElement { @Override public FieldElement substring(int start) { - return new StrutFieldElement(0); + return new StrutFieldElement(baseElement, 0); } @Override public FieldElement substring(int start, int end) { - return new StrutFieldElement(0); + return new StrutFieldElement(baseElement, 0); } @Override 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 527553165e..11606f3a5b 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 @@ -446,7 +446,7 @@ public class VerticalLayoutTextField implements TextField { if (tooManyLines && (i == maxLines - 1)) { FieldElement[] elements = new FieldElement[2]; elements[0] = element; - elements[1] = new StrutFieldElement(500); + elements[1] = new StrutFieldElement(element, 500); element = new CompositeFieldElement(elements); } TextField field = createFieldForLine(element); @@ -459,7 +459,6 @@ public class VerticalLayoutTextField implements TextField { } isClipped |= tooManyLines; - return newSubFields; }