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

@ -605,15 +605,17 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
// @formatter:off // @formatter:off
return new DomainObjectListenerBuilder(this) return new DomainObjectListenerBuilder(this)
.ignoreWhen(this::ignoreEvents) .ignoreWhen(this::ignoreEvents)
.any(RESTORED).terminate(this::reloadTree) .any(RESTORED).terminate(this::reloadTree)
.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

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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,10 +320,11 @@ 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;
private EventType[] eachEventTypes; // all the "onEach" event types private EventType[] eachEventTypes; // all the "onEach" event types
BuilderDomainObjectListener(String name) { BuilderDomainObjectListener(String name) {
this.name = name; this.name = name;
@ -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;