Merge remote-tracking branch 'origin/GP-5826-dragonmacher-symbol-tree-flickering'

This commit is contained in:
Ryan Kurtz 2025-07-15 10:58:40 -04:00
commit 4d6bb0ddaa
3 changed files with 57 additions and 13 deletions

View file

@ -609,11 +609,13 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
.with(ProgramChangeRecord.class) .with(ProgramChangeRecord.class)
.each(SYMBOL_RENAMED).call(this::processSymbolRenamed) .each(SYMBOL_RENAMED).call(this::processSymbolRenamed)
.each(SYMBOL_DATA_CHANGED, SYMBOL_SCOPE_CHANGED).call(this::processSymbolChanged) .each(SYMBOL_DATA_CHANGED, SYMBOL_SCOPE_CHANGED).call(this::processSymbolChanged)
.each(FUNCTION_CHANGED).call(this::processFunctionChanged)
.each(SYMBOL_ADDED).call(this::processSymbolAdded) .each(SYMBOL_ADDED).call(this::processSymbolAdded)
.each(SYMBOL_REMOVED).call(this::processSymbolRemoved) .each(SYMBOL_REMOVED).call(this::processSymbolRemoved)
.each(EXTERNAL_ENTRY_ADDED, EXTERNAL_ENTRY_REMOVED) .each(EXTERNAL_ENTRY_ADDED, EXTERNAL_ENTRY_REMOVED)
.call(this::processExternalEntryChanged) .call(this::processExternalEntryChanged)
// handle function changes specially so that we can perform coalesce changes
.any(FUNCTION_CHANGED).call(this::processAllFunction)
.build(); .build();
// @formatter:on // @formatter:on
} }
@ -626,10 +628,24 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
symbolRemoved((Symbol) pcr.getObject()); symbolRemoved((Symbol) pcr.getObject());
} }
private void processFunctionChanged(ProgramChangeRecord pcr) { private void processAllFunction(DomainObjectChangedEvent e) {
Function function = (Function) pcr.getObject();
Symbol symbol = function.getSymbol(); // grab all function records and remove duplicates so that we can make 1 task for all
symbolChanged(symbol); // changes to a given function
Set<Symbol> 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) { private void processSymbolChanged(ProgramChangeRecord pcr) {
@ -851,5 +867,4 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
task.run(monitor); task.run(monitor);
} }
} }
} }

View file

@ -27,7 +27,6 @@ import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
import generic.theme.GColor; import generic.theme.GColor;
import generic.theme.GColorUIResource; import generic.theme.GColorUIResource;
import ghidra.util.HTMLUtilities;
public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent { public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent {
@ -71,10 +70,16 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent
GTreeNode node = (GTreeNode) value; GTreeNode node = (GTreeNode) value;
String text = node.getDisplayText(); String text = node.getDisplayText();
setText(text); setText(text);
String toolTip = node.getToolTip(); String toolTip = node.getToolTip();
setToolTipText(toolTip); 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); Icon icon = getNodeIcon(node, expanded);
if (icon == null) { if (icon == null) {

View file

@ -21,6 +21,7 @@ import java.util.function.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import utilities.util.reflection.ReflectionUtilities; import utilities.util.reflection.ReflectionUtilities;
import utility.function.Callback; import utility.function.Callback;
import utility.function.Dummy;
/** /**
* Base class for creating a compact and efficient {@link DomainObjectListener}s. See * Base class for creating a compact and efficient {@link DomainObjectListener}s. See
@ -33,6 +34,7 @@ import utility.function.Callback;
public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObjectChangeRecord, B extends AbstractDomainObjectListenerBuilder<R, B>> { public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObjectChangeRecord, B extends AbstractDomainObjectListenerBuilder<R, B>> {
private String name; private String name;
private BooleanSupplier ignoreCheck; private BooleanSupplier ignoreCheck;
private Consumer<DomainObjectChangedEvent> debugConsumer;
private List<EventTrigger> terminateList = new ArrayList<>(); private List<EventTrigger> terminateList = new ArrayList<>();
private List<EventTrigger> onAnyList = new ArrayList<>(); private List<EventTrigger> onAnyList = new ArrayList<>();
private Map<EventType, TypedRecordConsumer<? extends DomainObjectChangeRecord>> onEachMap = private Map<EventType, TypedRecordConsumer<? extends DomainObjectChangeRecord>> onEachMap =
@ -63,6 +65,17 @@ public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObject
protected abstract B self(); protected abstract B self();
/**
* Sets a consumer of events that is intended to be used for clients to add a callback for each
* event. This is useful for temporarily inspecting events and adding breakpoints.
* @param consumer the consumer to add
* @return this builder (for chaining)
*/
public B debug(Consumer<DomainObjectChangedEvent> consumer) {
this.debugConsumer = consumer;
return self();
}
/** /**
* Sets a boolean supplier that can be checked to see if the client is in a state where * 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. * they don't want events to be processed at this time.
@ -121,6 +134,7 @@ public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObject
BuilderDomainObjectListener listener = new BuilderDomainObjectListener(name); BuilderDomainObjectListener listener = new BuilderDomainObjectListener(name);
listener.setIgnoreCheck(ignoreCheck); listener.setIgnoreCheck(ignoreCheck);
listener.setDebugConsumer(debugConsumer);
if (!terminateList.isEmpty()) { if (!terminateList.isEmpty()) {
listener.setTerminateList(terminateList); listener.setTerminateList(terminateList);
} }
@ -306,6 +320,7 @@ public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObject
static class BuilderDomainObjectListener implements DomainObjectListener { static class BuilderDomainObjectListener implements DomainObjectListener {
private String name; private String name;
private BooleanSupplier ignoreCheck = () -> false; private BooleanSupplier ignoreCheck = () -> false;
private Consumer<DomainObjectChangedEvent> debugConsumer = Dummy.consumer();
private List<EventTrigger> terminateList; private List<EventTrigger> terminateList;
private List<EventTrigger> onAnyList; private List<EventTrigger> onAnyList;
private Map<EventType, TypedRecordConsumer<? extends DomainObjectChangeRecord>> onEachMap; private Map<EventType, TypedRecordConsumer<? extends DomainObjectChangeRecord>> onEachMap;
@ -323,6 +338,10 @@ public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObject
this.ignoreCheck = supplier != null ? supplier : () -> false; this.ignoreCheck = supplier != null ? supplier : () -> false;
} }
void setDebugConsumer(Consumer<DomainObjectChangedEvent> debugConsumer) {
this.debugConsumer = Dummy.ifNull(debugConsumer);
}
void setTerminateList(List<EventTrigger> terminatEventList) { void setTerminateList(List<EventTrigger> terminatEventList) {
this.terminateList = terminatEventList; this.terminateList = terminatEventList;
} }
@ -339,10 +358,15 @@ public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObject
@Override @Override
public void domainObjectChanged(DomainObjectChangedEvent event) { public void domainObjectChanged(DomainObjectChangedEvent event) {
// a way for clients to add conditional debug, print statements and breakpoints
debugConsumer.accept(event);
// check if events are being ignored // check if events are being ignored
if (ignoreCheck.getAsBoolean()) { if (ignoreCheck.getAsBoolean()) {
return; return;
} }
// check for terminating events first // check for terminating events first
if (terminateList != null && processTerminateList(event)) { if (terminateList != null && processTerminateList(event)) {
return; return;