GP-5826 - Symbol Tree - Fixed flashing due to too many events being

processed
This commit is contained in:
dragonmacher 2025-07-12 14:19:48 -04:00
parent c7aa190b40
commit 95fd25eb58
3 changed files with 57 additions and 13 deletions

View file

@ -609,11 +609,13 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
.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<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) {
@ -851,5 +867,4 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
task.run(monitor);
}
}
}

View file

@ -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) {

View file

@ -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<R extends DomainObjectChangeRecord, B extends AbstractDomainObjectListenerBuilder<R, B>> {
private String name;
private BooleanSupplier ignoreCheck;
private Consumer<DomainObjectChangedEvent> debugConsumer;
private List<EventTrigger> terminateList = new ArrayList<>();
private List<EventTrigger> onAnyList = new ArrayList<>();
private Map<EventType, TypedRecordConsumer<? extends DomainObjectChangeRecord>> onEachMap =
@ -63,6 +65,17 @@ public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObject
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
* 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);
listener.setIgnoreCheck(ignoreCheck);
listener.setDebugConsumer(debugConsumer);
if (!terminateList.isEmpty()) {
listener.setTerminateList(terminateList);
}
@ -306,6 +320,7 @@ public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObject
static class BuilderDomainObjectListener implements DomainObjectListener {
private String name;
private BooleanSupplier ignoreCheck = () -> false;
private Consumer<DomainObjectChangedEvent> debugConsumer = Dummy.consumer();
private List<EventTrigger> terminateList;
private List<EventTrigger> onAnyList;
private Map<EventType, TypedRecordConsumer<? extends DomainObjectChangeRecord>> onEachMap;
@ -323,6 +338,10 @@ public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObject
this.ignoreCheck = supplier != null ? supplier : () -> false;
}
void setDebugConsumer(Consumer<DomainObjectChangedEvent> debugConsumer) {
this.debugConsumer = Dummy.ifNull(debugConsumer);
}
void setTerminateList(List<EventTrigger> terminatEventList) {
this.terminateList = terminatEventList;
}
@ -339,10 +358,15 @@ public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObject
@Override
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
if (ignoreCheck.getAsBoolean()) {
return;
}
// check for terminating events first
if (terminateList != null && processTerminateList(event)) {
return;