diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java index f35437b26b..0ac10c0dcb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java @@ -605,15 +605,17 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { // @formatter:off return new DomainObjectListenerBuilder(this) .ignoreWhen(this::ignoreEvents) - .any(RESTORED).terminate(this::reloadTree) + .any(RESTORED).terminate(this::reloadTree) .with(ProgramChangeRecord.class) .each(SYMBOL_RENAMED).call(this::processSymbolRenamed) .each(SYMBOL_DATA_CHANGED, SYMBOL_SCOPE_CHANGED).call(this::processSymbolChanged) - .each(FUNCTION_CHANGED).call(this::processFunctionChanged) .each(SYMBOL_ADDED).call(this::processSymbolAdded) .each(SYMBOL_REMOVED).call(this::processSymbolRemoved) .each(EXTERNAL_ENTRY_ADDED, EXTERNAL_ENTRY_REMOVED) .call(this::processExternalEntryChanged) + + // handle function changes specially so that we can perform coalesce changes + .any(FUNCTION_CHANGED).call(this::processAllFunction) .build(); // @formatter:on } @@ -626,10 +628,24 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { symbolRemoved((Symbol) pcr.getObject()); } - private void processFunctionChanged(ProgramChangeRecord pcr) { - Function function = (Function) pcr.getObject(); - Symbol symbol = function.getSymbol(); - symbolChanged(symbol); + private void processAllFunction(DomainObjectChangedEvent e) { + + // grab all function records and remove duplicates so that we can make 1 task for all + // changes to a given function + Set symbols = new HashSet<>(); + int n = e.numRecords(); + for (int i = 0; i < n; i++) { + DomainObjectChangeRecord r = e.getChangeRecord(i); + if (r instanceof FunctionChangeRecord fcr) { + Function f = fcr.getFunction(); + Symbol s = f.getSymbol(); + symbols.add(s); + } + } + + for (Symbol s : symbols) { + symbolChanged(s); + } } private void processSymbolChanged(ProgramChangeRecord pcr) { @@ -851,5 +867,4 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { task.run(monitor); } } - } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java index 11123ca97c..d806a0f45a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java @@ -27,7 +27,6 @@ import docking.widgets.tree.GTree; import docking.widgets.tree.GTreeNode; import generic.theme.GColor; import generic.theme.GColorUIResource; -import ghidra.util.HTMLUtilities; public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent { @@ -71,10 +70,16 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent GTreeNode node = (GTreeNode) value; String text = node.getDisplayText(); setText(text); + String toolTip = node.getToolTip(); setToolTipText(toolTip); - String fromHTML = HTMLUtilities.fromHTML(toolTip); - getAccessibleContext().setAccessibleDescription(fromHTML); + + // Note: attempting to use the tooltip text here by removing the html formatting can be + // too slow for large functions. Also, the full preview text for larger symbol tool tips + // can be overwhelming for screen readers. The text of the node is a reasonable value + // to provide for screen readers. + // String fromHTML = HTMLUtilities.fromHTML(toolTip); + getAccessibleContext().setAccessibleDescription(node.getDisplayText()); Icon icon = getNodeIcon(node, expanded); if (icon == null) { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/AbstractDomainObjectListenerBuilder.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/AbstractDomainObjectListenerBuilder.java index 2c27ba19a6..7ecbf165cb 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/AbstractDomainObjectListenerBuilder.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/AbstractDomainObjectListenerBuilder.java @@ -4,9 +4,9 @@ * 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. @@ -21,6 +21,7 @@ import java.util.function.*; import ghidra.util.Msg; import utilities.util.reflection.ReflectionUtilities; import utility.function.Callback; +import utility.function.Dummy; /** * Base class for creating a compact and efficient {@link DomainObjectListener}s. See @@ -33,6 +34,7 @@ import utility.function.Callback; public abstract class AbstractDomainObjectListenerBuilder> { private String name; private BooleanSupplier ignoreCheck; + private Consumer debugConsumer; private List terminateList = new ArrayList<>(); private List onAnyList = new ArrayList<>(); private Map> onEachMap = @@ -63,6 +65,17 @@ public abstract class AbstractDomainObjectListenerBuilder consumer) { + this.debugConsumer = consumer; + return self(); + } + /** * Sets a boolean supplier that can be checked to see if the client is in a state where * they don't want events to be processed at this time. @@ -121,6 +134,7 @@ public abstract class AbstractDomainObjectListenerBuilder false; + private Consumer debugConsumer = Dummy.consumer(); private List terminateList; private List onAnyList; private Map> onEachMap; - private EventType[] eachEventTypes; // all the "onEach" event types + private EventType[] eachEventTypes; // all the "onEach" event types BuilderDomainObjectListener(String name) { this.name = name; @@ -323,6 +338,10 @@ public abstract class AbstractDomainObjectListenerBuilder false; } + void setDebugConsumer(Consumer debugConsumer) { + this.debugConsumer = Dummy.ifNull(debugConsumer); + } + void setTerminateList(List terminatEventList) { this.terminateList = terminatEventList; } @@ -339,10 +358,15 @@ public abstract class AbstractDomainObjectListenerBuilder