GP-3018: Navigate/activate by double-click. Various related fixes.
|
@ -71,7 +71,7 @@ public class DebugSystemObjectsImpl1 implements DebugSystemObjectsInternal {
|
||||||
@Override
|
@Override
|
||||||
public void setCurrentThreadId(DebugThreadId id) {
|
public void setCurrentThreadId(DebugThreadId id) {
|
||||||
HRESULT hr = jnaSysobj.SetCurrentThreadId(new ULONG(id.id));
|
HRESULT hr = jnaSysobj.SetCurrentThreadId(new ULONG(id.id));
|
||||||
if (!hr.equals(COMUtilsExtra.E_UNEXPECTED)) {
|
if (!hr.equals(COMUtilsExtra.E_UNEXPECTED) && !hr.equals(COMUtilsExtra.E_NOINTERFACE)) {
|
||||||
COMUtils.checkRC(hr);
|
COMUtils.checkRC(hr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ public interface DbgModelSelectableObject extends DbgModelTargetObject {
|
||||||
}
|
}
|
||||||
if (this instanceof DbgModelTargetStackFrame) {
|
if (this instanceof DbgModelTargetStackFrame) {
|
||||||
DbgModelTargetStackFrame tf = (DbgModelTargetStackFrame) this;
|
DbgModelTargetStackFrame tf = (DbgModelTargetStackFrame) this;
|
||||||
TargetObject ref = tf.getThread();
|
TargetObject ref = tf.getParentThread();
|
||||||
if (ref instanceof DbgModelTargetThread) {
|
if (ref instanceof DbgModelTargetThread) {
|
||||||
DbgModelTargetThread tt = (DbgModelTargetThread) ref;
|
DbgModelTargetThread tt = (DbgModelTargetThread) ref;
|
||||||
DbgThread thread = tt.getThread();
|
DbgThread thread = tt.getThread();
|
||||||
|
|
|
@ -18,13 +18,10 @@ package agent.dbgeng.model.iface2;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import agent.dbgeng.manager.DbgEventsListenerAdapter;
|
import agent.dbgeng.manager.*;
|
||||||
import agent.dbgeng.manager.DbgStackFrame;
|
|
||||||
import agent.dbgeng.manager.impl.DbgManagerImpl;
|
import agent.dbgeng.manager.impl.DbgManagerImpl;
|
||||||
import agent.dbgeng.manager.impl.DbgThreadImpl;
|
|
||||||
import agent.dbgeng.model.iface1.DbgModelSelectableObject;
|
import agent.dbgeng.model.iface1.DbgModelSelectableObject;
|
||||||
import ghidra.async.AsyncUtils;
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.dbg.target.TargetObject;
|
|
||||||
import ghidra.dbg.target.TargetStackFrame;
|
import ghidra.dbg.target.TargetStackFrame;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSpace;
|
import ghidra.program.model.address.AddressSpace;
|
||||||
|
@ -51,7 +48,8 @@ public interface DbgModelTargetStackFrame extends //
|
||||||
@Override
|
@Override
|
||||||
public default CompletableFuture<Void> setActive() {
|
public default CompletableFuture<Void> setActive() {
|
||||||
DbgManagerImpl manager = getManager();
|
DbgManagerImpl manager = getManager();
|
||||||
DbgThreadImpl thread = manager.getCurrentThread();
|
DbgModelTargetThread targetThread = getParentThread();
|
||||||
|
DbgThread thread = targetThread.getThread();
|
||||||
String name = this.getName();
|
String name = this.getName();
|
||||||
String stripped = name.substring(1, name.length() - 1);
|
String stripped = name.substring(1, name.length() - 1);
|
||||||
int index = Integer.decode(stripped);
|
int index = Integer.decode(stripped);
|
||||||
|
@ -111,10 +109,6 @@ public interface DbgModelTargetStackFrame extends //
|
||||||
|
|
||||||
public void setFrame(DbgStackFrame frame);
|
public void setFrame(DbgStackFrame frame);
|
||||||
|
|
||||||
public TargetObject getThread();
|
|
||||||
|
|
||||||
public Address getPC();
|
public Address getPC();
|
||||||
|
|
||||||
public DbgModelTargetProcess getProcess();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ import agent.dbgeng.manager.*;
|
||||||
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
|
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
|
||||||
import agent.dbgeng.model.iface2.*;
|
import agent.dbgeng.model.iface2.*;
|
||||||
import ghidra.dbg.target.TargetFocusScope;
|
import ghidra.dbg.target.TargetFocusScope;
|
||||||
import ghidra.dbg.target.TargetObject;
|
|
||||||
import ghidra.dbg.target.schema.*;
|
import ghidra.dbg.target.schema.*;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
@ -79,8 +78,6 @@ public class DbgModelTargetStackFrameImpl extends DbgModelTargetObjectImpl
|
||||||
return PathUtils.makeKey(indexFrame(frame));
|
return PathUtils.makeKey(indexFrame(frame));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final DbgModelTargetThread thread;
|
|
||||||
|
|
||||||
protected DbgStackFrame frame;
|
protected DbgStackFrame frame;
|
||||||
protected Address pc;
|
protected Address pc;
|
||||||
protected String func;
|
protected String func;
|
||||||
|
@ -97,7 +94,6 @@ public class DbgModelTargetStackFrameImpl extends DbgModelTargetObjectImpl
|
||||||
DbgStackFrame frame) {
|
DbgStackFrame frame) {
|
||||||
super(stack.getModel(), stack, keyFrame(frame), "StackFrame");
|
super(stack.getModel(), stack, keyFrame(frame), "StackFrame");
|
||||||
this.getModel().addModelObject(frame, this);
|
this.getModel().addModelObject(frame, this);
|
||||||
this.thread = thread;
|
|
||||||
this.pc = getModel().getAddressSpace("ram").getAddress(-1);
|
this.pc = getModel().getAddressSpace("ram").getAddress(-1);
|
||||||
|
|
||||||
changeAttributes(List.of(), List.of(), Map.of( //
|
changeAttributes(List.of(), List.of(), Map.of( //
|
||||||
|
@ -162,21 +158,11 @@ public class DbgModelTargetStackFrameImpl extends DbgModelTargetObjectImpl
|
||||||
), "Refreshed");
|
), "Refreshed");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public TargetObject getThread() {
|
|
||||||
return thread.getParent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Address getPC() {
|
public Address getPC() {
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public DbgModelTargetProcess getProcess() {
|
|
||||||
return ((DbgModelTargetThreadImpl) thread).getProcess();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
public void invalidateRegisterCaches() {
|
public void invalidateRegisterCaches() {
|
||||||
listeners.fire.invalidateCacheRequested(this);
|
listeners.fire.invalidateCacheRequested(this);
|
||||||
|
|
|
@ -264,7 +264,8 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
||||||
* or be used as an example for other implementations.
|
* or be used as an example for other implementations.
|
||||||
*/
|
*/
|
||||||
if (!PathUtils.isAncestor(this.getPath(), obj.getPath())) {
|
if (!PathUtils.isAncestor(this.getPath(), obj.getPath())) {
|
||||||
throw new DebuggerIllegalArgumentException("Can only focus a successor of the scope");
|
throw new DebuggerIllegalArgumentException(
|
||||||
|
"Can only activate a successor of the scope");
|
||||||
}
|
}
|
||||||
TargetObject cur = obj;
|
TargetObject cur = obj;
|
||||||
while (cur != null) {
|
while (cur != null) {
|
||||||
|
|
|
@ -75,8 +75,11 @@ public class GdbModelTargetStackFrame
|
||||||
|
|
||||||
this.registers = new GdbModelTargetStackFrameRegisterContainer(this);
|
this.registers = new GdbModelTargetStackFrameRegisterContainer(this);
|
||||||
|
|
||||||
changeAttributes(List.of(), List.of(registers), Map.of( //
|
changeAttributes(List.of(),
|
||||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame)),
|
List.of(
|
||||||
|
registers),
|
||||||
|
Map.of(
|
||||||
|
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame)),
|
||||||
"Initialized");
|
"Initialized");
|
||||||
setFrame(frame);
|
setFrame(frame);
|
||||||
}
|
}
|
||||||
|
@ -99,14 +102,14 @@ public class GdbModelTargetStackFrame
|
||||||
this.func = frame.getFunction();
|
this.func = frame.getFunction();
|
||||||
// TODO: module? "from"
|
// TODO: module? "from"
|
||||||
|
|
||||||
changeAttributes(List.of(), List.of( //
|
changeAttributes(List.of(),
|
||||||
registers //
|
List.of(
|
||||||
), Map.of( //
|
registers),
|
||||||
PC_ATTRIBUTE_NAME, pc, //
|
Map.of(
|
||||||
FUNC_ATTRIBUTE_NAME, func, //
|
PC_ATTRIBUTE_NAME, pc,
|
||||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame), //
|
FUNC_ATTRIBUTE_NAME, func,
|
||||||
VALUE_ATTRIBUTE_NAME, pc //
|
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame)),
|
||||||
), "Refreshed");
|
"Refreshed");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void invalidateRegisterCaches() {
|
protected void invalidateRegisterCaches() {
|
||||||
|
|
|
@ -35,6 +35,18 @@
|
||||||
because it descends from an inferior or process. In almost every case, the selection directly
|
because it descends from an inferior or process. In almost every case, the selection directly
|
||||||
or indirectly determines the set of valid or enabled actions.</P>
|
or indirectly determines the set of valid or enabled actions.</P>
|
||||||
|
|
||||||
|
<P>The tree will display the active object, e.g., the current thread, and its ancestors in
|
||||||
|
<B>bold</B> font. By default, this is also the selected object. To activate a different object,
|
||||||
|
double-click it or select it and press ENTER. This will command the debugger and the rest of
|
||||||
|
the UI to make that object the current object. For example, double-clicking the 2nd thread
|
||||||
|
would be equivalent to <CODE>thread 2</CODE> in GDB, or <CODE>~2s</CODE> in WinDbg. The actions
|
||||||
|
apply to the <EM>selected</EM> object, which may differ from the <EM>active</EM> object. For
|
||||||
|
example, suppose Thread 1 is active. If you click "Step Into," it will command the debugger to
|
||||||
|
step Thread 1. If you select (but do not activate) Thread 2 and click "Step Into," it will
|
||||||
|
command the debugger to step Thread 2, even though Thread 1 is still the active thread. Likely,
|
||||||
|
Thread 2 will become active during the Step Into command, but that behavior is determined by
|
||||||
|
the model implementation.</P>
|
||||||
|
|
||||||
<P>The application of these special properties to each object to determine its behavior and
|
<P>The application of these special properties to each object to determine its behavior and
|
||||||
relevant actions allows all objects to be treated generically. This feature has several
|
relevant actions allows all objects to be treated generically. This feature has several
|
||||||
powerful implications. All objects may be represented in many ways, and one representation may
|
powerful implications. All objects may be represented in many ways, and one representation may
|
||||||
|
@ -170,6 +182,12 @@
|
||||||
"help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html">Breakpoint
|
"help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html">Breakpoint
|
||||||
Marker</A> actions from the disassembly listings.</P>
|
Marker</A> actions from the disassembly listings.</P>
|
||||||
|
|
||||||
|
<H3><A name="toggle_option"></A><IMG alt="" src="images/system-switch-user.png"> Toggle
|
||||||
|
Option</H3>
|
||||||
|
|
||||||
|
<P>Toggle an object. This may apply to a breakpoint or to configuration options provided in the
|
||||||
|
model tree.</P>
|
||||||
|
|
||||||
<H2>Actions for Trace Management</H2>
|
<H2>Actions for Trace Management</H2>
|
||||||
|
|
||||||
<P>The following actions manage target tracing. Note that many other windows are not usable
|
<P>The following actions manage target tracing. Note that many other windows are not usable
|
||||||
|
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
|
@ -25,13 +25,15 @@
|
||||||
<P>The stack window displays the current trace's execution stack, as unwound and reported by
|
<P>The stack window displays the current trace's execution stack, as unwound and reported by
|
||||||
the target. Not all debuggers will unwind the stack, in which case, this window displays only
|
the target. Not all debuggers will unwind the stack, in which case, this window displays only
|
||||||
the innermost frame. When emulation is used to generate the current machine state, only a
|
the innermost frame. When emulation is used to generate the current machine state, only a
|
||||||
single synthetic frame is shown. Level 0 always refers to the innermost frame, and each
|
single synthetic frame is shown. See the <A href="#unwind_stack">Unwind Stack</A> action for an
|
||||||
incremental level refers to the next caller in the chain — most of the time. The current
|
alternative mechanism that unwinds using Ghidra's program databases and works with emulation.
|
||||||
frame comprises one element of the tool's current "coordinates." Selecting a frame changes
|
Level 0 always refers to the innermost frame, and each incremental level refers to the next
|
||||||
those coordinates, potentially causing other windows to display different information. Namely,
|
caller in the chain — most of the time. The current frame comprises one element of the
|
||||||
the <A href="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A>
|
tool's current "coordinates." Double-clicking a frame changes those coordinates, potentially
|
||||||
window will show registers for the current frame, assuming they can be retrieved. The Listings
|
causing other windows to display different information. Namely, the <A href=
|
||||||
may also navigate to the current frame's program counter.</P>
|
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window will
|
||||||
|
show registers for the current frame, assuming they can be retrieved. The Listings may also
|
||||||
|
navigate to the current frame's program counter.</P>
|
||||||
|
|
||||||
<H2>Table Columns</H2>
|
<H2>Table Columns</H2>
|
||||||
|
|
||||||
|
@ -41,8 +43,7 @@
|
||||||
<LI>Level - the level of the frame, counting 0-up starting at the innermost frame.</LI>
|
<LI>Level - the level of the frame, counting 0-up starting at the innermost frame.</LI>
|
||||||
|
|
||||||
<LI>PC - the address of the instruction to execute next, or upon return of the callee for
|
<LI>PC - the address of the instruction to execute next, or upon return of the callee for
|
||||||
non-0 frames. Different platforms may have different subtleties in how they report PC.
|
non-0 frames. Different platforms may have different subtleties in how they report PC.</LI>
|
||||||
Double-clicking this field will navigate to the address.</LI>
|
|
||||||
|
|
||||||
<LI>Function - the name of the containing function, if Ghidra has the corresponding module
|
<LI>Function - the name of the containing function, if Ghidra has the corresponding module
|
||||||
image imported and analyzed.</LI>
|
image imported and analyzed.</LI>
|
||||||
|
@ -66,7 +67,8 @@
|
||||||
and unwind it in the same manner. This proceeds until analysis fails or the stack segment is
|
and unwind it in the same manner. This proceeds until analysis fails or the stack segment is
|
||||||
exhausted. For best results, ensure you have imported and opened the Ghidra program database
|
exhausted. For best results, ensure you have imported and opened the Ghidra program database
|
||||||
for every module, or at least the subset you expect to see in your stack. To view the results,
|
for every module, or at least the subset you expect to see in your stack. To view the results,
|
||||||
navigate to or follow the stack pointer in a Dynamic Listing.</P>
|
navigate to or follow the stack pointer in a Dynamic Listing. The Stack window <EM>does
|
||||||
|
not</EM> display Ghidra's unwinding results.</P>
|
||||||
|
|
||||||
<TABLE width="100%">
|
<TABLE width="100%">
|
||||||
<TBODY>
|
<TBODY>
|
||||||
|
@ -101,8 +103,8 @@
|
||||||
<CODE>posOff_</CODE> for positive offsets). They represent unused or unknown entries.</LI>
|
<CODE>posOff_</CODE> for positive offsets). They represent unused or unknown entries.</LI>
|
||||||
</UL>
|
</UL>
|
||||||
|
|
||||||
<P>The frame entries are not automatically updated when a function's frame changes in a program
|
<P>The frame entries are <EM>not</EM> automatically updated when a function's frame changes in
|
||||||
database. To update the unwind after changing a function's stack frame, you must unwind
|
a program database. To update the unwind after changing a function's stack frame, you must
|
||||||
again.</P>
|
unwind again.</P>
|
||||||
</BODY>
|
</BODY>
|
||||||
</HTML>
|
</HTML>
|
||||||
|
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 8.8 KiB |
|
@ -53,8 +53,8 @@
|
||||||
|
|
||||||
<H2>Navigating Threads</H2>
|
<H2>Navigating Threads</H2>
|
||||||
|
|
||||||
<P>Selecting a thread in the table will navigate to (or "activate" or "focus") that thread.
|
<P>Double-clicking a thread in the table will navigate to (or "activate" or "focus") that
|
||||||
Windows which are sensitive to the current thread will update. Notably, the <A href=
|
thread. Windows which are sensitive to the current thread will update. Notably, the <A href=
|
||||||
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window will
|
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window will
|
||||||
display the activated thread's register values. <A href=
|
display the activated thread's register values. <A href=
|
||||||
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Listing</A> windows with
|
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Listing</A> windows with
|
||||||
|
@ -88,15 +88,15 @@
|
||||||
|
|
||||||
<H2>Actions</H2>
|
<H2>Actions</H2>
|
||||||
|
|
||||||
<H3><A name="sync_focus"></A>Synchronize Trace and Target Focus</H3>
|
<H3><A name="sync_target"></A>Synchronize Trace and Target Activation</H3>
|
||||||
|
|
||||||
<P>This toggle is always available and is enabled by default. While enabled, any changes in
|
<P>This toggle is always available and is enabled by default. While enabled, any changes in
|
||||||
navigation coordinates are translated, to the extent possible, and sent to the connected
|
navigation coordinates are translated, to the extent possible, and sent to the connected
|
||||||
debugger. This may, for example, issue <CODE>thread</CODE> and/or <CODE>frame</CODE> commands
|
debugger. This may, for example, issue <CODE>thread</CODE> and/or <CODE>frame</CODE> commands
|
||||||
to GDB so that commands typed into its CLI will refer to the same thread and frame as is
|
to GDB so that commands typed into its CLI will refer to the same thread and frame as is active
|
||||||
focused in Ghidra. Conversely, any debugger events which indicate a change in focus are
|
in Ghidra. Conversely, any target events which indicate a change in activation are translated,
|
||||||
translated, to the extent possible, into navigation coordinates and activated in Ghidra. For
|
to the extent possible, into navigation coordinates and activated in Ghidra. For example, if
|
||||||
example, if the user issues a <CODE>frame</CODE> command to the CLI of a GDB connection, then
|
the user issues a <CODE>frame</CODE> command to the CLI of a GDB connection, then Ghidra will
|
||||||
Ghidra will navigate to that same frame.</P>
|
navigate to that same frame.</P>
|
||||||
</BODY>
|
</BODY>
|
||||||
</HTML>
|
</HTML>
|
||||||
|
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
@ -26,9 +26,9 @@
|
||||||
snapshot per event recorded. Other windows often display the times of various events or use
|
snapshot per event recorded. Other windows often display the times of various events or use
|
||||||
time ranges to describe lifespans of various records. Those times refer to the "snap," which is
|
time ranges to describe lifespans of various records. Those times refer to the "snap," which is
|
||||||
a 0-up counter of snapshot records. Thus, a snapshot is a collection of observations of a
|
a 0-up counter of snapshot records. Thus, a snapshot is a collection of observations of a
|
||||||
target's state, usually while suspended, along with any mark up. Selecting a snapshot navigates
|
target's state, usually while suspended, along with any mark up. Double-clicking a snapshot
|
||||||
to the selected point in time. Note that browsing the past may prevent other windows from
|
navigates to the selected point in time. Note that navigating to the past may change your <A
|
||||||
interacting with a live target.</P>
|
href="help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control mode</A>.</P>
|
||||||
|
|
||||||
<H2>Table Columns</H2>
|
<H2>Table Columns</H2>
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
@ -1669,11 +1669,11 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SynchronizeFocusAction {
|
interface SynchronizeTargetAction {
|
||||||
String NAME = "Synchronize Focus";
|
String NAME = "Synchronize Target Activation";
|
||||||
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
|
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
|
||||||
Icon ICON = ICON_SYNC;
|
Icon ICON = ICON_SYNC;
|
||||||
String HELP_ANCHOR = "sync_focus";
|
String HELP_ANCHOR = "sync_target";
|
||||||
|
|
||||||
static ToggleActionBuilder builder(Plugin owner) {
|
static ToggleActionBuilder builder(Plugin owner) {
|
||||||
String ownerName = owner.getName();
|
String ownerName = owner.getName();
|
||||||
|
|
|
@ -101,6 +101,7 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
||||||
|
|
||||||
private Trace trace;
|
private Trace trace;
|
||||||
private long snap;
|
private long snap;
|
||||||
|
private TraceObject curObject;
|
||||||
private Trace diffTrace;
|
private Trace diffTrace;
|
||||||
private long diffSnap;
|
private long diffSnap;
|
||||||
private ModelQuery query;
|
private ModelQuery query;
|
||||||
|
@ -173,6 +174,23 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
||||||
return snap;
|
return snap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void currentObjectChanged() {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentObject(TraceObject curObject) {
|
||||||
|
if (this.curObject == curObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.curObject = curObject;
|
||||||
|
|
||||||
|
currentObjectChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraceObject getCurrentObject() {
|
||||||
|
return curObject;
|
||||||
|
}
|
||||||
|
|
||||||
protected void diffTraceChanged() {
|
protected void diffTraceChanged() {
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
||||||
table.addMouseListener(new MouseAdapter() {
|
table.addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(MouseEvent e) {
|
public void mouseClicked(MouseEvent e) {
|
||||||
if (e.getClickCount() == 2) {
|
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||||
fireCellActivated();
|
fireCellActivated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,13 +90,16 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
||||||
DebuggerCoordinates previous = current;
|
DebuggerCoordinates previous = current;
|
||||||
this.current = coords;
|
this.current = coords;
|
||||||
if (previous.getSnap() == current.getSnap() &&
|
if (previous.getSnap() == current.getSnap() &&
|
||||||
previous.getTrace() == current.getTrace()) {
|
previous.getTrace() == current.getTrace() &&
|
||||||
|
previous.getObject() == current.getObject()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tableModel.setDiffTrace(previous.getTrace());
|
tableModel.setDiffTrace(previous.getTrace());
|
||||||
tableModel.setTrace(current.getTrace());
|
tableModel.setTrace(current.getTrace());
|
||||||
tableModel.setDiffSnap(previous.getSnap());
|
tableModel.setDiffSnap(previous.getSnap());
|
||||||
tableModel.setSnap(current.getSnap());
|
tableModel.setSnap(current.getSnap());
|
||||||
|
// current object is used only for bolding
|
||||||
|
tableModel.setCurrentObject(current.getObject());
|
||||||
if (limitToSnap) {
|
if (limitToSnap) {
|
||||||
tableModel.setSpan(Lifespan.at(current.getSnap()));
|
tableModel.setSpan(Lifespan.at(current.getSnap()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.gui.model;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||||
|
@ -112,12 +113,20 @@ public class DebuggerModelPlugin extends Plugin {
|
||||||
return Collections.unmodifiableList(disconnectedProviders);
|
return Collections.unmodifiableList(disconnectedProviders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void coordinatesActivated(DebuggerCoordinates current) {
|
||||||
|
connectedProvider.coordinatesActivated(current);
|
||||||
|
synchronized (disconnectedProviders) {
|
||||||
|
for (DebuggerModelProvider p : disconnectedProviders) {
|
||||||
|
p.coordinatesActivated(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processEvent(PluginEvent event) {
|
public void processEvent(PluginEvent event) {
|
||||||
super.processEvent(event);
|
super.processEvent(event);
|
||||||
if (event instanceof TraceActivatedPluginEvent) {
|
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
coordinatesActivated(ev.getActiveCoordinates());
|
||||||
connectedProvider.coordinatesActivated(ev.getActiveCoordinates());
|
|
||||||
}
|
}
|
||||||
if (event instanceof TraceClosedPluginEvent) {
|
if (event instanceof TraceClosedPluginEvent) {
|
||||||
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
||||||
|
|
|
@ -47,6 +47,7 @@ import ghidra.framework.plugintool.AutoConfigState;
|
||||||
import ghidra.framework.plugintool.AutoService;
|
import ghidra.framework.plugintool.AutoService;
|
||||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.*;
|
import ghidra.trace.model.target.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
@ -155,7 +156,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
public boolean verify(JComponent input) {
|
public boolean verify(JComponent input) {
|
||||||
try {
|
try {
|
||||||
TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText());
|
TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText());
|
||||||
setPath(path, pathField, EventOrigin.USER_GENERATED);
|
setPath(path);
|
||||||
|
objectsTreePanel.setSelectedKeyPaths(List.of(path));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException e) {
|
catch (IllegalArgumentException e) {
|
||||||
|
@ -168,7 +170,7 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
ActionListener gotoPath = evt -> {
|
ActionListener gotoPath = evt -> {
|
||||||
try {
|
try {
|
||||||
TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText());
|
TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText());
|
||||||
setPath(path, pathField, EventOrigin.USER_GENERATED);
|
activatePath(path);
|
||||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
|
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException e) {
|
catch (IllegalArgumentException e) {
|
||||||
|
@ -238,20 +240,30 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
myActionContext = null;
|
myActionContext = null;
|
||||||
}
|
}
|
||||||
contextChanged();
|
contextChanged();
|
||||||
|
|
||||||
if (sel.size() != 1) {
|
if (sel.size() != 1) {
|
||||||
// TODO: Multiple paths? PathMatcher can do it, just have to parse
|
|
||||||
// Just leave whatever was there.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TraceObjectValue value = sel.get(0).getValue();
|
TraceObjectValue value = sel.get(0).getValue();
|
||||||
TraceObjectKeyPath path = value.getCanonicalPath();
|
setPath(value.getCanonicalPath(), objectsTreePanel);
|
||||||
|
|
||||||
// Prevent activation when selecting a link
|
|
||||||
EventOrigin origin =
|
|
||||||
value.isCanonical() ? evt.getEventOrigin() : EventOrigin.API_GENERATED;
|
|
||||||
setPath(path, objectsTreePanel, origin);
|
|
||||||
});
|
});
|
||||||
|
objectsTreePanel.tree.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||||
|
activateObjectSelectedInTree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
objectsTreePanel.tree.tree().addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
|
activateObjectSelectedInTree();
|
||||||
|
e.consume(); // lest is select the next row down
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
elementsTablePanel.addSelectionListener(evt -> {
|
elementsTablePanel.addSelectionListener(evt -> {
|
||||||
if (evt.getValueIsAdjusting()) {
|
if (evt.getValueIsAdjusting()) {
|
||||||
return;
|
return;
|
||||||
|
@ -278,9 +290,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
}
|
}
|
||||||
TraceObject object = value.getChild();
|
TraceObject object = value.getChild();
|
||||||
attributesTablePanel.setQuery(ModelQuery.attributesOf(object.getCanonicalPath()));
|
attributesTablePanel.setQuery(ModelQuery.attributesOf(object.getCanonicalPath()));
|
||||||
if (value.isCanonical()) {
|
|
||||||
activatePath(object.getCanonicalPath());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
attributesTablePanel.addSelectionListener(evt -> {
|
attributesTablePanel.addSelectionListener(evt -> {
|
||||||
if (evt.getValueIsAdjusting()) {
|
if (evt.getValueIsAdjusting()) {
|
||||||
|
@ -297,15 +306,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
myActionContext = null;
|
myActionContext = null;
|
||||||
}
|
}
|
||||||
contextChanged();
|
contextChanged();
|
||||||
|
|
||||||
if (sel.size() != 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceObjectValue value = sel.get(0).getPath().getLastEntry();
|
|
||||||
// "canonical" implies "object"
|
|
||||||
if (value != null && value.isCanonical()) {
|
|
||||||
activatePath(value.getCanonicalPath());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
elementsTablePanel.addCellActivationListener(elementActivationListener);
|
elementsTablePanel.addCellActivationListener(elementActivationListener);
|
||||||
|
@ -315,6 +315,19 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
attributesTablePanel.addSeekListener(seekListener);
|
attributesTablePanel.addSeekListener(seekListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void activateObjectSelectedInTree() {
|
||||||
|
List<AbstractNode> sel = objectsTreePanel.getSelectedItems();
|
||||||
|
if (sel.size() != 1) {
|
||||||
|
// TODO: Multiple paths? PathMatcher can do it, just have to parse
|
||||||
|
// Just leave whatever was there.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TraceObjectValue value = sel.get(0).getValue();
|
||||||
|
if (value.getValue() instanceof TraceObject child) {
|
||||||
|
activatePath(child.getCanonicalPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ActionContext getActionContext(MouseEvent event) {
|
public ActionContext getActionContext(MouseEvent event) {
|
||||||
if (myActionContext != null) {
|
if (myActionContext != null) {
|
||||||
|
@ -352,11 +365,10 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
if (row == null) {
|
if (row == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(row instanceof ObjectRow)) {
|
if (!(row instanceof ObjectRow objectRow)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ObjectRow objectRow = (ObjectRow) row;
|
activatePath(objectRow.getTraceObject().getCanonicalPath());
|
||||||
setPath(objectRow.getTraceObject().getCanonicalPath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activatedAttributesTable() {
|
private void activatedAttributesTable() {
|
||||||
|
@ -365,11 +377,10 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Object value = row.getValue();
|
Object value = row.getValue();
|
||||||
if (!(value instanceof TraceObject)) {
|
if (!(value instanceof TraceObject object)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TraceObject object = (TraceObject) value;
|
activatePath(object.getCanonicalPath());
|
||||||
setPath(object.getCanonicalPath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activatedCloneWindow() {
|
private void activatedCloneWindow() {
|
||||||
|
@ -419,7 +430,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
if (values.size() != 1) {
|
if (values.size() != 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setPath(values.get(0).getChild().getCanonicalPath(), null, EventOrigin.USER_GENERATED);
|
TraceObjectKeyPath canonicalPath = values.get(0).getChild().getCanonicalPath();
|
||||||
|
setPath(canonicalPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -476,11 +488,15 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
elementsTablePanel.goToCoordinates(coords);
|
elementsTablePanel.goToCoordinates(coords);
|
||||||
attributesTablePanel.goToCoordinates(coords);
|
attributesTablePanel.goToCoordinates(coords);
|
||||||
|
|
||||||
|
checkPath();
|
||||||
|
|
||||||
|
if (isClone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// NOTE: The plugin only calls this on the connected provider
|
// NOTE: The plugin only calls this on the connected provider
|
||||||
// When cloning or restoring state, we MUST still consider the object
|
// When cloning or restoring state, we MUST still consider the object
|
||||||
TraceObject object = coords.getObject();
|
TraceObject object = coords.getObject();
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
checkPath();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (attributesTablePanel.trySelect(object)) {
|
if (attributesTablePanel.trySelect(object)) {
|
||||||
|
@ -490,14 +506,15 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (findAsParent(object) != null) {
|
if (findAsParent(object) != null) {
|
||||||
checkPath();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TraceObjectKeyPath sibling = findAsSibling(object);
|
TraceObjectKeyPath sibling = findAsSibling(object);
|
||||||
if (sibling != null) {
|
if (sibling != null) {
|
||||||
|
objectsTreePanel.setSelectedKeyPaths(List.of(sibling));
|
||||||
setPath(sibling);
|
setPath(sibling);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
objectsTreePanel.setSelectedObject(object);
|
||||||
setPath(object.getCanonicalPath());
|
setPath(object.getCanonicalPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -509,48 +526,52 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void activatePath(TraceObjectKeyPath path) {
|
protected void activatePath(TraceObjectKeyPath path) {
|
||||||
if (isClone) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Trace trace = current.getTrace();
|
Trace trace = current.getTrace();
|
||||||
if (trace != null) {
|
if (trace != null) {
|
||||||
TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(path);
|
TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(path);
|
||||||
if (object != null) {
|
if (object != null) {
|
||||||
traceManager.activateObject(object);
|
traceManager.activateObject(object);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
object = trace.getObjectManager()
|
||||||
|
.getObjectsByPath(Lifespan.at(current.getSnap()), path)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (object != null) {
|
||||||
|
traceManager.activateObject(object);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setPath(TraceObjectKeyPath path, JComponent source, EventOrigin origin) {
|
protected void setPath(TraceObjectKeyPath path, JComponent source) {
|
||||||
if (Objects.equals(this.path, path) && getTreeSelection() != null) {
|
if (Objects.equals(this.path, path) && getTreeSelection() != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.path = path;
|
this.path = path;
|
||||||
if (source != pathField) {
|
|
||||||
pathField.setText(path.toString());
|
|
||||||
}
|
|
||||||
if (source != objectsTreePanel) {
|
if (source != objectsTreePanel) {
|
||||||
setTreeSelection(path);
|
setTreeSelection(path);
|
||||||
}
|
}
|
||||||
if (origin == EventOrigin.USER_GENERATED) {
|
|
||||||
activatePath(path);
|
pathField.setText(path.toString());
|
||||||
}
|
objectsTreePanel.repaint();
|
||||||
elementsTablePanel.setQuery(ModelQuery.elementsOf(path));
|
elementsTablePanel.setQuery(ModelQuery.elementsOf(path));
|
||||||
attributesTablePanel.setQuery(ModelQuery.attributesOf(path));
|
attributesTablePanel.setQuery(ModelQuery.attributesOf(path));
|
||||||
|
|
||||||
checkPath();
|
checkPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPath(TraceObjectKeyPath path) {
|
||||||
|
setPath(path, null);
|
||||||
|
}
|
||||||
|
|
||||||
protected void checkPath() {
|
protected void checkPath() {
|
||||||
if (objectsTreePanel.getNode(path) == null) {
|
if (objectsTreePanel.getNode(path) == null) {
|
||||||
plugin.getTool().setStatusInfo("No such object at path " + path, true);
|
plugin.getTool().setStatusInfo("No such object at path " + path, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPath(TraceObjectKeyPath path) {
|
|
||||||
setPath(path, null, EventOrigin.API_GENERATED);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TraceObjectKeyPath getPath() {
|
public TraceObjectKeyPath getPath() {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,6 +181,8 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
public interface ValueRow {
|
public interface ValueRow {
|
||||||
String getKey();
|
String getKey();
|
||||||
|
|
||||||
|
TraceObject currentObject();
|
||||||
|
|
||||||
long currentSnap();
|
long currentSnap();
|
||||||
|
|
||||||
long previousSnap();
|
long previousSnap();
|
||||||
|
@ -212,6 +214,8 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
*/
|
*/
|
||||||
boolean isModified();
|
boolean isModified();
|
||||||
|
|
||||||
|
boolean isCurrent();
|
||||||
|
|
||||||
default <T> ValueAttribute<T> getAttribute(String attributeName, Class<T> type) {
|
default <T> ValueAttribute<T> getAttribute(String attributeName, Class<T> type) {
|
||||||
return new ValueAttribute<>(this, attributeName, type);
|
return new ValueAttribute<>(this, attributeName, type);
|
||||||
}
|
}
|
||||||
|
@ -250,6 +254,11 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
return getSnap();
|
return getSnap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TraceObject currentObject() {
|
||||||
|
return getCurrentObject();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long previousSnap() {
|
public long previousSnap() {
|
||||||
return getTrace() == getDiffTrace() ? getDiffSnap() : getSnap();
|
return getTrace() == getDiffTrace() ? getDiffSnap() : getSnap();
|
||||||
|
@ -313,6 +322,11 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
public boolean isAttributeModified(String attributeName) {
|
public boolean isAttributeModified(String attributeName) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCurrent() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class ObjectRow extends AbstractValueRow {
|
protected class ObjectRow extends AbstractValueRow {
|
||||||
|
@ -366,6 +380,15 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
public boolean isAttributeModified(String attributeName) {
|
public boolean isAttributeModified(String attributeName) {
|
||||||
return isValueModified(getAttributeEntry(attributeName));
|
return isValueModified(getAttributeEntry(attributeName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCurrent() {
|
||||||
|
TraceObject current = getCurrentObject();
|
||||||
|
if (current == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return object.getCanonicalPath().isAncestor(current.getCanonicalPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ValueRow rowForValue(TraceObjectValue value) {
|
protected ValueRow rowForValue(TraceObjectValue value) {
|
||||||
|
@ -375,46 +398,13 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
return new PrimitiveRow(value);
|
return new PrimitiveRow(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class ColKey {
|
protected record ColKey(String name, Class<?> type) {
|
||||||
public static ColKey fromSchema(SchemaContext ctx, AttributeSchema attributeSchema) {
|
public static ColKey fromSchema(SchemaContext ctx, AttributeSchema attributeSchema) {
|
||||||
String name = attributeSchema.getName();
|
String name = attributeSchema.getName();
|
||||||
Class<?> type =
|
Class<?> type =
|
||||||
TraceValueObjectAttributeColumn.computeAttributeType(ctx, attributeSchema);
|
TraceValueObjectAttributeColumn.computeAttributeType(ctx, attributeSchema);
|
||||||
return new ColKey(name, type);
|
return new ColKey(name, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final Class<?> type;
|
|
||||||
private final int hash;
|
|
||||||
|
|
||||||
public ColKey(String name, Class<?> type) {
|
|
||||||
this.name = name;
|
|
||||||
this.type = type;
|
|
||||||
this.hash = Objects.hash(name, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (obj == this) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(obj instanceof ColKey)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ColKey that = (ColKey) obj;
|
|
||||||
if (!Objects.equals(this.name, that.name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.type != that.type) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class AutoAttributeColumn<T> extends TraceValueObjectAttributeColumn<T> {
|
static class AutoAttributeColumn<T> extends TraceValueObjectAttributeColumn<T> {
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
package ghidra.app.plugin.core.debug.gui.model;
|
package ghidra.app.plugin.core.debug.gui.model;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.MouseListener;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.*;
|
import java.util.stream.*;
|
||||||
|
@ -26,6 +25,7 @@ import javax.swing.JTree;
|
||||||
import javax.swing.tree.TreePath;
|
import javax.swing.tree.TreePath;
|
||||||
|
|
||||||
import docking.widgets.tree.GTree;
|
import docking.widgets.tree.GTree;
|
||||||
|
import docking.widgets.tree.GTreeNode;
|
||||||
import docking.widgets.tree.support.GTreeRenderer;
|
import docking.widgets.tree.support.GTreeRenderer;
|
||||||
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
||||||
import docking.widgets.tree.support.GTreeSelectionListener;
|
import docking.widgets.tree.support.GTreeSelectionListener;
|
||||||
|
@ -34,7 +34,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
import ghidra.trace.model.target.*;
|
||||||
|
|
||||||
public class ObjectsTreePanel extends JPanel {
|
public class ObjectsTreePanel extends JPanel {
|
||||||
|
|
||||||
|
@ -43,6 +43,21 @@ public class ObjectsTreePanel extends JPanel {
|
||||||
setHTMLRenderingEnabled(true);
|
setHTMLRenderingEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isOnCurrentPath(TraceObjectValue value) {
|
||||||
|
if (value == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (value.getValue() instanceof TraceObject child && isOnCurrentPath(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOnCurrentPath(TraceObject object) {
|
||||||
|
TraceObject cur = current.getObject();
|
||||||
|
if (cur == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return object.getCanonicalPath().isAncestor(cur.getCanonicalPath());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
|
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
|
||||||
boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
||||||
|
@ -54,6 +69,7 @@ public class ObjectsTreePanel extends JPanel {
|
||||||
|
|
||||||
AbstractNode node = (AbstractNode) value;
|
AbstractNode node = (AbstractNode) value;
|
||||||
setForeground(getForegroundFor(tree, node.isModified(), selected));
|
setForeground(getForegroundFor(tree, node.isModified(), selected));
|
||||||
|
setFont(getFont(isOnCurrentPath(node.getValue())));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +84,19 @@ public class ObjectsTreePanel extends JPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class ObjectGTree extends GTree {
|
||||||
|
public ObjectGTree(GTreeNode root) {
|
||||||
|
super(root);
|
||||||
|
getJTree().setToggleClickCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
JTree tree() {
|
||||||
|
return getJTree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected final ObjectTreeModel treeModel;
|
protected final ObjectTreeModel treeModel;
|
||||||
protected final GTree tree;
|
protected final ObjectGTree tree;
|
||||||
|
|
||||||
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
protected boolean limitToSnap = true;
|
protected boolean limitToSnap = true;
|
||||||
|
@ -83,7 +110,7 @@ public class ObjectsTreePanel extends JPanel {
|
||||||
public ObjectsTreePanel() {
|
public ObjectsTreePanel() {
|
||||||
super(new BorderLayout());
|
super(new BorderLayout());
|
||||||
treeModel = createModel();
|
treeModel = createModel();
|
||||||
tree = new GTree(treeModel.getRoot());
|
tree = new ObjectGTree(treeModel.getRoot());
|
||||||
|
|
||||||
tree.setCellRenderer(new ObjectsTreeRenderer());
|
tree.setCellRenderer(new ObjectsTreeRenderer());
|
||||||
add(tree, BorderLayout.CENTER);
|
add(tree, BorderLayout.CENTER);
|
||||||
|
@ -108,14 +135,14 @@ public class ObjectsTreePanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void goToCoordinates(DebuggerCoordinates coords) {
|
public void goToCoordinates(DebuggerCoordinates coords) {
|
||||||
// TODO: thread should probably become a TraceObject once we transition
|
|
||||||
if (DebuggerCoordinates.equalsIgnoreRecorderAndView(current, coords)) {
|
if (DebuggerCoordinates.equalsIgnoreRecorderAndView(current, coords)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DebuggerCoordinates previous = current;
|
DebuggerCoordinates previous = current;
|
||||||
this.current = coords;
|
this.current = coords;
|
||||||
if (previous.getSnap() == current.getSnap() &&
|
if (previous.getSnap() == current.getSnap() &&
|
||||||
previous.getTrace() == current.getTrace()) {
|
previous.getTrace() == current.getTrace() &&
|
||||||
|
previous.getObject() == current.getObject()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try (KeepTreeState keep = keepTreeState()) {
|
try (KeepTreeState keep = keepTreeState()) {
|
||||||
|
@ -127,6 +154,7 @@ public class ObjectsTreePanel extends JPanel {
|
||||||
treeModel.setSpan(Lifespan.at(current.getSnap()));
|
treeModel.setSpan(Lifespan.at(current.getSnap()));
|
||||||
}
|
}
|
||||||
tree.filterChanged();
|
tree.filterChanged();
|
||||||
|
// Repaint for bold current path is already going to happen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,20 +238,6 @@ public class ObjectsTreePanel extends JPanel {
|
||||||
tree.removeGTreeSelectionListener(listener);
|
tree.removeGTreeSelectionListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void addMouseListener(MouseListener l) {
|
|
||||||
super.addMouseListener(l);
|
|
||||||
// Is this a HACK?
|
|
||||||
tree.addMouseListener(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void removeMouseListener(MouseListener l) {
|
|
||||||
super.removeMouseListener(l);
|
|
||||||
// HACK?
|
|
||||||
tree.removeMouseListener(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectionMode(int selectionMode) {
|
public void setSelectionMode(int selectionMode) {
|
||||||
tree.getSelectionModel().setSelectionMode(selectionMode);
|
tree.getSelectionModel().setSelectionMode(selectionMode);
|
||||||
}
|
}
|
||||||
|
@ -272,4 +286,30 @@ public class ObjectsTreePanel extends JPanel {
|
||||||
public void setSelectedKeyPaths(Collection<TraceObjectKeyPath> keyPaths) {
|
public void setSelectedKeyPaths(Collection<TraceObjectKeyPath> keyPaths) {
|
||||||
setSelectedKeyPaths(keyPaths, EventOrigin.API_GENERATED);
|
setSelectedKeyPaths(keyPaths, EventOrigin.API_GENERATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void expandCurrent() {
|
||||||
|
TraceObject object = current.getObject();
|
||||||
|
if (object == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AbstractNode node = getNode(object.getCanonicalPath());
|
||||||
|
TreePath parentPath = node.getTreePath().getParentPath();
|
||||||
|
if (parentPath != null) {
|
||||||
|
tree.expandPath(parentPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setSelectedObject(TraceObject object) {
|
||||||
|
if (object == null) {
|
||||||
|
tree.clearSelectionPaths();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AbstractNode node = getNode(object.getCanonicalPath());
|
||||||
|
tree.addSelectionPath(node.getTreePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectCurrent() {
|
||||||
|
setSelectedObject(current.getObject());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,17 @@ public class PathTableModel extends AbstractQueryTableModel<PathRow> {
|
||||||
// Root is canonical
|
// Root is canonical
|
||||||
return last == null || last.isCanonical();
|
return last == null || last.isCanonical();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCurrent() {
|
||||||
|
TraceObject current = getCurrentObject();
|
||||||
|
if (current == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(getValue() instanceof TraceObject child)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return child.getCanonicalPath().isAncestor(current.getCanonicalPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PathTableModel(Plugin plugin) {
|
public PathTableModel(Plugin plugin) {
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
|
||||||
|
import docking.widgets.table.GTableCellRenderingData;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
|
|
||||||
|
public class TracePathColumnRenderer<T> extends AbstractGColumnRenderer<T> {
|
||||||
|
@Override
|
||||||
|
public String getFilterString(T t, Settings settings) {
|
||||||
|
return t == null ? "<null>" : t.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
super.getTableCellRendererComponent(data);
|
||||||
|
PathRow row = (PathRow) data.getRowObject();
|
||||||
|
if (row.isCurrent()) {
|
||||||
|
setBold();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,13 +22,21 @@ import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.TraceObjectValPath;
|
import ghidra.trace.model.target.TraceObjectValPath;
|
||||||
import ghidra.trace.model.target.TraceObjectValue;
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
public class TracePathLastKeyColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> {
|
public class TracePathLastKeyColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> {
|
||||||
|
private final TracePathColumnRenderer<String> renderer = new TracePathColumnRenderer<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
return "Key";
|
return "Key";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GColumnRenderer<String> getColumnRenderer() {
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getValue(PathRow rowObject, Settings settings, Trace data,
|
public String getValue(PathRow rowObject, Settings settings, Trace data,
|
||||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
|
|
|
@ -15,22 +15,51 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.model.columns;
|
package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
|
||||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||||
|
import docking.widgets.table.GTableCellRenderingData;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.TraceObjectValue;
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
public class TracePathLastLifespanColumn
|
public class TracePathLastLifespanColumn
|
||||||
extends AbstractDynamicTableColumn<PathRow, Lifespan, Trace> {
|
extends AbstractDynamicTableColumn<PathRow, Lifespan, Trace> {
|
||||||
|
|
||||||
|
private final class LastLifespanRenderer extends AbstractGColumnRenderer<Lifespan> {
|
||||||
|
@Override
|
||||||
|
public String getFilterString(Lifespan t, Settings settings) {
|
||||||
|
return t == null ? "<null>" : t.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
super.getTableCellRendererComponent(data);
|
||||||
|
PathRow row = (PathRow) data.getRowObject();
|
||||||
|
if (row.isCurrent()) {
|
||||||
|
setBold();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final LastLifespanRenderer renderer = new LastLifespanRenderer();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
return "Life";
|
return "Life";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GColumnRenderer<Lifespan> getColumnRenderer() {
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Lifespan getValue(PathRow rowObject, Settings settings, Trace data,
|
public Lifespan getValue(PathRow rowObject, Settings settings, Trace data,
|
||||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
|
|
|
@ -21,13 +21,21 @@ import ghidra.dbg.util.PathUtils;
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
public class TracePathStringColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> {
|
public class TracePathStringColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> {
|
||||||
|
private final TracePathColumnRenderer<String> renderer = new TracePathColumnRenderer<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
return "Path";
|
return "Path";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GColumnRenderer<String> getColumnRenderer() {
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getValue(PathRow rowObject, Settings settings, Trace data,
|
public String getValue(PathRow rowObject, Settings settings, Trace data,
|
||||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
|
|
|
@ -50,6 +50,9 @@ public class TracePathValueColumn extends AbstractDynamicTableColumn<PathRow, Pa
|
||||||
setText(row.getHtmlDisplay());
|
setText(row.getHtmlDisplay());
|
||||||
setToolTipText(row.getToolTip());
|
setToolTipText(row.getToolTip());
|
||||||
setForeground(getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
|
setForeground(getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
|
||||||
|
if (row.isCurrent()) {
|
||||||
|
setBold();
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
|
||||||
|
import docking.widgets.table.GTableCellRenderingData;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
|
|
||||||
|
public class TraceValueColumnRenderer<T> extends AbstractGColumnRenderer<T> {
|
||||||
|
@Override
|
||||||
|
public String getFilterString(T t, Settings settings) {
|
||||||
|
return t == null ? "<null>" : t.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
super.getTableCellRendererComponent(data);
|
||||||
|
ValueRow row = (ValueRow) data.getRowObject();
|
||||||
|
if (row.isCurrent()) {
|
||||||
|
setBold();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,8 +23,11 @@ import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
public class TraceValueKeyColumn extends AbstractDynamicTableColumn<ValueRow, String, Trace> {
|
public class TraceValueKeyColumn extends AbstractDynamicTableColumn<ValueRow, String, Trace> {
|
||||||
|
private final TraceValueColumnRenderer<String> renderer = new TraceValueColumnRenderer<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
return "Key";
|
return "Key";
|
||||||
|
@ -36,6 +39,11 @@ public class TraceValueKeyColumn extends AbstractDynamicTableColumn<ValueRow, St
|
||||||
return rowObject.getKey();
|
return rowObject.getKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GColumnRenderer<String> getColumnRenderer() {
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Comparator<String> getComparator() {
|
public Comparator<String> getComparator() {
|
||||||
return TargetObjectKeyComparator.CHILD;
|
return TargetObjectKeyComparator.CHILD;
|
||||||
|
|
|
@ -21,15 +21,22 @@ import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.trace.model.Lifespan.LifeSet;
|
import ghidra.trace.model.Lifespan.LifeSet;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
public class TraceValueLifeColumn
|
public class TraceValueLifeColumn
|
||||||
extends AbstractDynamicTableColumn<ValueRow, LifeSet, Trace> {
|
extends AbstractDynamicTableColumn<ValueRow, LifeSet, Trace> {
|
||||||
|
private final TraceValueColumnRenderer<LifeSet> renderer = new TraceValueColumnRenderer<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
return "Life";
|
return "Life";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GColumnRenderer<LifeSet> getColumnRenderer() {
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LifeSet getValue(ValueRow rowObject, Settings settings, Trace data,
|
public LifeSet getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
|
|
|
@ -26,10 +26,6 @@ import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
|
||||||
import ghidra.dbg.target.schema.SchemaContext;
|
import ghidra.dbg.target.schema.SchemaContext;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||||
import ghidra.docking.settings.Settings;
|
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
|
||||||
import ghidra.trace.model.Lifespan;
|
|
||||||
import ghidra.trace.model.Trace;
|
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -56,8 +56,10 @@ public abstract class TraceValueObjectPropertyColumn<T>
|
||||||
ValueProperty<T> p = (ValueProperty<T>) data.getValue();
|
ValueProperty<T> p = (ValueProperty<T>) data.getValue();
|
||||||
setText(p.getHtmlDisplay());
|
setText(p.getHtmlDisplay());
|
||||||
setToolTipText(p.getToolTip());
|
setToolTipText(p.getToolTip());
|
||||||
|
|
||||||
setForeground(getForegroundFor(data.getTable(), p.isModified(), data.isSelected()));
|
setForeground(getForegroundFor(data.getTable(), p.isModified(), data.isSelected()));
|
||||||
|
if (p.getRow().isCurrent()) {
|
||||||
|
setBold();
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,9 @@ public class TraceValueValColumn extends AbstractDynamicTableColumn<ValueRow, Va
|
||||||
setToolTipText(row.getToolTip());
|
setToolTipText(row.getToolTip());
|
||||||
setForeground(
|
setForeground(
|
||||||
getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
|
getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
|
||||||
|
if (row.isCurrent()) {
|
||||||
|
setBold();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (TraceClosedException e) {
|
catch (TraceClosedException e) {
|
||||||
setText("ERROR: Trace Closed");
|
setText("ERROR: Trace Closed");
|
||||||
|
|
|
@ -150,6 +150,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
protected Map<Long, Trace> traces = new HashMap<>();
|
protected Map<Long, Trace> traces = new HashMap<>();
|
||||||
protected Trace currentTrace;
|
protected Trace currentTrace;
|
||||||
protected DebuggerObjectModel currentModel;
|
protected DebuggerObjectModel currentModel;
|
||||||
|
private TargetObject targetFocus;
|
||||||
// NB: We're getting rid of this because the ObjectsProvider is beating the trace
|
// NB: We're getting rid of this because the ObjectsProvider is beating the trace
|
||||||
// to the punch and causing the pattern-matcher to fail
|
// to the punch and causing the pattern-matcher to fail
|
||||||
// private TraceRecorder recorder;
|
// private TraceRecorder recorder;
|
||||||
|
@ -562,17 +563,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
synchronized (targetMap) {
|
synchronized (targetMap) {
|
||||||
targetMap.put(key, container);
|
targetMap.put(key, container);
|
||||||
refSet.add(targetObject);
|
refSet.add(targetObject);
|
||||||
|
if (targetObject instanceof TargetConfigurable) {
|
||||||
|
configurables.add((TargetConfigurable) targetObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (targetObject instanceof TargetInterpreter) {
|
if (targetObject instanceof TargetInterpreter) {
|
||||||
TargetInterpreter interpreter = (TargetInterpreter) targetObject;
|
TargetInterpreter interpreter = (TargetInterpreter) targetObject;
|
||||||
getPlugin().showConsole(interpreter);
|
getPlugin().showConsole(interpreter);
|
||||||
DebugModelConventions.findSuitable(TargetFocusScope.class, targetObject)
|
pane.setSelectedObject(targetObject);
|
||||||
.thenAccept(f -> {
|
|
||||||
setFocus(f, targetObject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (targetObject instanceof TargetConfigurable) {
|
|
||||||
configurables.add((TargetConfigurable) targetObject);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -591,7 +589,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectContainer getContainerByPath(List<String> path) {
|
public ObjectContainer getContainerByPath(List<String> path) {
|
||||||
return targetMap.get(PathUtils.toString(path));
|
return targetMap.get(PathUtils.toString(path, PATH_JOIN_CHAR));
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<ObjectContainer> getContainersFromObjects(Map<String, ?> objectMap,
|
static List<ObjectContainer> getContainersFromObjects(Map<String, ?> objectMap,
|
||||||
|
@ -1724,13 +1722,13 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
|
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
|
||||||
public void executionStateChanged(TargetObject object, TargetExecutionState state) {
|
public void executionStateChanged(TargetObject object, TargetExecutionState state) {
|
||||||
//this.state = state;
|
//this.state = state;
|
||||||
plugin.getTool().contextChanged(DebuggerObjectsProvider.this);
|
contextChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
|
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
|
||||||
public void focusChanged(TargetObject object, TargetObject focused) {
|
public void focusChanged(TargetObject object, TargetObject focused) {
|
||||||
plugin.setFocus(object, focused);
|
plugin.setFocus(object, focused);
|
||||||
plugin.getTool().contextChanged(DebuggerObjectsProvider.this);
|
contextChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1824,8 +1822,11 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
if (focused.getModel() != currentModel) {
|
if (focused.getModel() != currentModel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.targetFocus = focused;
|
||||||
if (isStopped(focused) || isUpdateWhileRunning()) {
|
if (isStopped(focused) || isUpdateWhileRunning()) {
|
||||||
pane.setFocus(object, focused);
|
if (pane != null) {
|
||||||
|
pane.setFocus(object, focused);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1911,9 +1912,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
return listener.queue.in;
|
return listener.queue.in;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void navigateToSelectedObject(TargetObject object, Object value) {
|
public Address navigateToSelectedObject(TargetObject object, Object value) {
|
||||||
if (listingService == null || listingService == null) {
|
if (listingService == null || modelService == null) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
// TODO: Could probably inspect schema for any attribute of type Address[Range], or String
|
// TODO: Could probably inspect schema for any attribute of type Address[Range], or String
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -1926,7 +1927,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
value = object.getCachedAttribute(TargetObject.VALUE_ATTRIBUTE_NAME);
|
value = object.getCachedAttribute(TargetObject.VALUE_ATTRIBUTE_NAME);
|
||||||
}
|
}
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Address addr = null;
|
Address addr = null;
|
||||||
|
@ -1947,13 +1948,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
if (recorder == null) {
|
if (recorder == null) {
|
||||||
recorder = modelService.getRecorder(currentTrace);
|
recorder = modelService.getRecorder(currentTrace);
|
||||||
if (recorder == null) {
|
if (recorder == null) {
|
||||||
return;
|
return addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DebuggerMemoryMapper memoryMapper = recorder.getMemoryMapper();
|
DebuggerMemoryMapper memoryMapper = recorder.getMemoryMapper();
|
||||||
Address traceAddr = memoryMapper.targetToTrace(addr);
|
Address traceAddr = memoryMapper.targetToTrace(addr);
|
||||||
listingService.goTo(traceAddr, true);
|
listingService.goTo(traceAddr, true);
|
||||||
}
|
}
|
||||||
|
return addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Address stringToAddress(TargetObject selectedObject, Address addr, String sval) {
|
private Address stringToAddress(TargetObject selectedObject, Address addr, String sval) {
|
||||||
|
@ -1976,4 +1978,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||||
public boolean isUpdateWhileRunning() {
|
public boolean isUpdateWhileRunning() {
|
||||||
return updateWhileRunning;
|
return updateWhileRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TargetObject getFocus() {
|
||||||
|
return targetFocus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -545,6 +545,17 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
|
||||||
return isLink;
|
return isLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFocused() {
|
||||||
|
if (provider == null || targetObject == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TargetObject focus = provider.getFocus();
|
||||||
|
if (focus == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return PathUtils.isAncestor(targetObject.getPath(), focus.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
public String getOrder() {
|
public String getOrder() {
|
||||||
Integer order = (Integer) attributeMap.get(TargetObject.ORDER_ATTRIBUTE_NAME);
|
Integer order = (Integer) attributeMap.get(TargetObject.ORDER_ATTRIBUTE_NAME);
|
||||||
return order == null ? getName() : Integer.toString(order);
|
return order == null ? getName() : Integer.toString(order);
|
||||||
|
@ -578,5 +589,4 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
|
||||||
}
|
}
|
||||||
return this.hashCode() - that.hashCode();
|
return this.hashCode() - that.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,7 @@ public abstract class DisplayAsAction extends DockingAction {
|
||||||
provider.getModel(), container, isTree);
|
provider.getModel(), container, isTree);
|
||||||
container.propagateProvider(p);
|
container.propagateProvider(p);
|
||||||
p.update(container);
|
p.update(container);
|
||||||
|
p.setFocus(null, provider.getFocus());
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
|
@ -23,7 +23,6 @@ import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
|
||||||
public interface ObjectPane {
|
public interface ObjectPane {
|
||||||
|
|
||||||
public ObjectContainer getContainer();
|
public ObjectContainer getContainer();
|
||||||
|
|
||||||
public TargetObject getTargetObject();
|
public TargetObject getTargetObject();
|
||||||
|
@ -46,6 +45,7 @@ public interface ObjectPane {
|
||||||
|
|
||||||
public void setFocus(TargetObject object, TargetObject focused);
|
public void setFocus(TargetObject object, TargetObject focused);
|
||||||
|
|
||||||
public void setRoot(ObjectContainer root, TargetObject targetObject);
|
public void setSelectedObject(TargetObject object);
|
||||||
|
|
||||||
|
public void setRoot(ObjectContainer root, TargetObject targetObject);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.objects.components;
|
package ghidra.app.plugin.core.debug.gui.objects.components;
|
||||||
|
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.*;
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
@ -26,7 +25,9 @@ import docking.widgets.table.EnumeratedColumnTableModel;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
|
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
|
||||||
import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
|
import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
|
||||||
|
import ghidra.dbg.DebugModelConventions;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
|
|
||||||
|
@ -34,11 +35,11 @@ public class ObjectTable<R> implements ObjectPane {
|
||||||
|
|
||||||
public static final Icon ICON_TABLE = new GIcon("icon.debugger.table.object");
|
public static final Icon ICON_TABLE = new GIcon("icon.debugger.table.object");
|
||||||
|
|
||||||
private ObjectContainer container;
|
private final ObjectContainer container;
|
||||||
private Class<R> clazz;
|
private final Class<R> clazz;
|
||||||
private AbstractSortedTableModel<R> model;
|
private final AbstractSortedTableModel<R> model;
|
||||||
private GhidraTable table;
|
private final GhidraTable table;
|
||||||
private JScrollPane component;
|
private final JScrollPane component;
|
||||||
|
|
||||||
public ObjectTable(ObjectContainer container, Class<R> clazz,
|
public ObjectTable(ObjectContainer container, Class<R> clazz,
|
||||||
AbstractSortedTableModel<R> model) {
|
AbstractSortedTableModel<R> model) {
|
||||||
|
@ -55,15 +56,24 @@ public class ObjectTable<R> implements ObjectPane {
|
||||||
DebuggerObjectsProvider provider = container.getProvider();
|
DebuggerObjectsProvider provider = container.getProvider();
|
||||||
provider.getTool().contextChanged(provider);
|
provider.getTool().contextChanged(provider);
|
||||||
});
|
});
|
||||||
|
table.setDefaultRenderer(String.class,
|
||||||
|
new ObjectTableCellRenderer(container.getProvider()));
|
||||||
|
table.setDefaultRenderer(Object.class,
|
||||||
|
new ObjectTableCellRenderer(container.getProvider()));
|
||||||
table.addMouseListener(new MouseAdapter() {
|
table.addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(MouseEvent e) {
|
public void mouseClicked(MouseEvent e) {
|
||||||
if (e.getClickCount() == 2) {
|
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||||
int selectedRow = table.getSelectedRow();
|
activateOrNavigateSelectedObject();
|
||||||
int selectedColumn = table.getSelectedColumn();
|
}
|
||||||
Object value = table.getValueAt(selectedRow, selectedColumn);
|
}
|
||||||
container.getProvider()
|
});
|
||||||
.navigateToSelectedObject(container.getTargetObject(), value);
|
table.addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
|
activateOrNavigateSelectedObject();
|
||||||
|
e.consume(); // lest it select the next row down
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -71,6 +81,38 @@ public class ObjectTable<R> implements ObjectPane {
|
||||||
signalUpdate(container);
|
signalUpdate(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void activateOrNavigateSelectedObject() {
|
||||||
|
int selectedRow = table.getSelectedRow();
|
||||||
|
int selectedColumn = table.getSelectedColumn();
|
||||||
|
Object value = table.getValueAt(selectedRow, selectedColumn);
|
||||||
|
if (container.getProvider()
|
||||||
|
.navigateToSelectedObject(container.getTargetObject(), value) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
R row = model.getModelData().get(selectedRow); // No filter?
|
||||||
|
TargetObject object;
|
||||||
|
if (row instanceof ObjectElementRow eRow) {
|
||||||
|
object = eRow.getTargetObject();
|
||||||
|
}
|
||||||
|
else if (row instanceof ObjectAttributeRow aRow) {
|
||||||
|
object = aRow.getTargetObject();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (object instanceof DummyTargetObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DebugModelConventions.requestActivation(object).exceptionally(ex -> {
|
||||||
|
Msg.error(this, "Could not activate " + object, ex);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
/*DebugModelConventions.requestFocus(object).exceptionally(ex -> {
|
||||||
|
Msg.error(this, "Could not focus " + object, ex);
|
||||||
|
return null;
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObjectContainer getContainer() {
|
public ObjectContainer getContainer() {
|
||||||
return container;
|
return container;
|
||||||
|
@ -171,8 +213,7 @@ public class ObjectTable<R> implements ObjectPane {
|
||||||
ObjectElementRow match = null;
|
ObjectElementRow match = null;
|
||||||
for (int i = 0; i < model.getRowCount(); i++) {
|
for (int i = 0; i < model.getRowCount(); i++) {
|
||||||
R r = model.getRowObject(i);
|
R r = model.getRowObject(i);
|
||||||
if (r instanceof ObjectElementRow) {
|
if (r instanceof ObjectElementRow row) {
|
||||||
ObjectElementRow row = (ObjectElementRow) r;
|
|
||||||
if (row.getTargetObject().equals(changedTarget)) {
|
if (row.getTargetObject().equals(changedTarget)) {
|
||||||
row.setAttributes(changed.getAttributeMap());
|
row.setAttributes(changed.getAttributeMap());
|
||||||
match = row;
|
match = row;
|
||||||
|
@ -184,7 +225,6 @@ public class ObjectTable<R> implements ObjectPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<R> updateMatch(ObjectElementRow match) {
|
private List<R> updateMatch(ObjectElementRow match) {
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
ObjectEnumeratedColumnTableModel<?, R> m = (ObjectEnumeratedColumnTableModel<?, R>) model;
|
ObjectEnumeratedColumnTableModel<?, R> m = (ObjectEnumeratedColumnTableModel<?, R>) model;
|
||||||
m.updateColumns(match);
|
m.updateColumns(match);
|
||||||
m.fireTableDataChanged();
|
m.fireTableDataChanged();
|
||||||
|
@ -198,12 +238,11 @@ public class ObjectTable<R> implements ObjectPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setColumns() {
|
public void setColumns() {
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
ObjectEnumeratedColumnTableModel<?, R> m = (ObjectEnumeratedColumnTableModel<?, R>) model;
|
ObjectEnumeratedColumnTableModel<?, R> m = (ObjectEnumeratedColumnTableModel<?, R>) model;
|
||||||
for (int i = 0; i < model.getRowCount(); i++) {
|
for (int i = 0; i < model.getRowCount(); i++) {
|
||||||
R r = model.getRowObject(i);
|
R r = model.getRowObject(i);
|
||||||
if (r instanceof ObjectElementRow) {
|
if (r instanceof ObjectElementRow row) {
|
||||||
m.updateColumns((ObjectElementRow) r);
|
m.updateColumns(row);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,12 +253,10 @@ public class ObjectTable<R> implements ObjectPane {
|
||||||
public TargetObject getSelectedObject() {
|
public TargetObject getSelectedObject() {
|
||||||
int selectedColumn = table.getSelectedColumn();
|
int selectedColumn = table.getSelectedColumn();
|
||||||
R r = model.getRowObject(table.getSelectedRow());
|
R r = model.getRowObject(table.getSelectedRow());
|
||||||
if (r instanceof ObjectAttributeRow) {
|
if (r instanceof ObjectAttributeRow row) {
|
||||||
ObjectAttributeRow row = (ObjectAttributeRow) r;
|
|
||||||
return row.getTargetObject();
|
return row.getTargetObject();
|
||||||
}
|
}
|
||||||
if (r instanceof ObjectElementRow) {
|
if (r instanceof ObjectElementRow row) {
|
||||||
ObjectElementRow row = (ObjectElementRow) r;
|
|
||||||
TargetObject targetObject = row.getTargetObject();
|
TargetObject targetObject = row.getTargetObject();
|
||||||
if (selectedColumn > 0) {
|
if (selectedColumn > 0) {
|
||||||
List<String> keys = row.getKeys();
|
List<String> keys = row.getKeys();
|
||||||
|
@ -229,8 +266,8 @@ public class ObjectTable<R> implements ObjectPane {
|
||||||
String key = keys.get(selectedColumn);
|
String key = keys.get(selectedColumn);
|
||||||
Map<String, ?> attributes = targetObject.getCachedAttributes();
|
Map<String, ?> attributes = targetObject.getCachedAttributes();
|
||||||
Object object = attributes.get(key);
|
Object object = attributes.get(key);
|
||||||
if (object instanceof TargetObject) {
|
if (object instanceof TargetObject to) {
|
||||||
return (TargetObject) object;
|
return to;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return targetObject;
|
return targetObject;
|
||||||
|
@ -238,18 +275,17 @@ public class ObjectTable<R> implements ObjectPane {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setSelectedObject(TargetObject selection) {
|
public void setSelectedObject(TargetObject selection) {
|
||||||
for (int i = 0; i < model.getRowCount(); i++) {
|
for (int i = 0; i < model.getRowCount(); i++) {
|
||||||
R r = model.getRowObject(i);
|
R r = model.getRowObject(i);
|
||||||
if (r instanceof ObjectAttributeRow) {
|
if (r instanceof ObjectAttributeRow row) {
|
||||||
ObjectAttributeRow row = (ObjectAttributeRow) r;
|
|
||||||
if (row.getTargetObject().equals(selection)) {
|
if (row.getTargetObject().equals(selection)) {
|
||||||
table.selectRow(i);
|
table.selectRow(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (r instanceof ObjectElementRow) {
|
if (r instanceof ObjectElementRow row) {
|
||||||
ObjectElementRow row = (ObjectElementRow) r;
|
|
||||||
if (row.getTargetObject().equals(selection)) {
|
if (row.getTargetObject().equals(selection)) {
|
||||||
table.selectRow(i);
|
table.selectRow(i);
|
||||||
break;
|
break;
|
||||||
|
@ -260,8 +296,9 @@ public class ObjectTable<R> implements ObjectPane {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFocus(TargetObject object, TargetObject focused) {
|
public void setFocus(TargetObject object, TargetObject focused) {
|
||||||
|
// Should this setSelectedObject, too?
|
||||||
Swing.runIfSwingOrRunLater(() -> {
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
setSelectedObject(focused);
|
table.repaint();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.objects.components;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
|
||||||
|
import docking.widgets.table.GTableCellRenderingData;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
|
||||||
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
|
import ghidra.util.table.column.AbstractGhidraColumnRenderer;
|
||||||
|
|
||||||
|
public class ObjectTableCellRenderer extends AbstractGhidraColumnRenderer<Object> {
|
||||||
|
private final DebuggerObjectsProvider provider;
|
||||||
|
|
||||||
|
public ObjectTableCellRenderer(DebuggerObjectsProvider provider) {
|
||||||
|
this.provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilterString(Object t, Settings settings) {
|
||||||
|
return t == null ? "<null>" : t.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
super.getTableCellRendererComponent(data);
|
||||||
|
TargetObject focus = provider.getFocus();
|
||||||
|
if (focus == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
Object rowObject = data.getRowObject();
|
||||||
|
TargetObject object;
|
||||||
|
if (rowObject instanceof ObjectElementRow eRow) {
|
||||||
|
object = eRow.getTargetObject();
|
||||||
|
}
|
||||||
|
else if (rowObject instanceof ObjectAttributeRow aRow) {
|
||||||
|
object = aRow.getTargetObject();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (PathUtils.isAncestor(object.getPath(), focus.getPath())) {
|
||||||
|
setBold();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,13 +16,11 @@
|
||||||
package ghidra.app.plugin.core.debug.gui.objects.components;
|
package ghidra.app.plugin.core.debug.gui.objects.components;
|
||||||
|
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.*;
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.*;
|
||||||
import javax.swing.JComponent;
|
|
||||||
import javax.swing.event.TreeExpansionEvent;
|
import javax.swing.event.TreeExpansionEvent;
|
||||||
import javax.swing.event.TreeExpansionListener;
|
import javax.swing.event.TreeExpansionListener;
|
||||||
import javax.swing.tree.TreePath;
|
import javax.swing.tree.TreePath;
|
||||||
|
@ -49,44 +47,38 @@ public class ObjectTree implements ObjectPane {
|
||||||
|
|
||||||
public static final Icon ICON_TREE = new GIcon("icon.debugger.tree.object");
|
public static final Icon ICON_TREE = new GIcon("icon.debugger.tree.object");
|
||||||
|
|
||||||
private ObjectNode root;
|
private static class MyGTree extends GTree {
|
||||||
private GTree tree;
|
public MyGTree(GTreeNode root) {
|
||||||
|
super(root);
|
||||||
|
getJTree().setToggleClickCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JTree tree() {
|
||||||
|
return getJTree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ObjectNode root;
|
||||||
|
private final MyGTree tree;
|
||||||
|
|
||||||
|
private final Map<String, ObjectNode> nodeMap = new LinkedHashMap<>();
|
||||||
|
private final SwingUpdateManager restoreTreeStateManager =
|
||||||
|
new SwingUpdateManager(this::restoreTreeState);
|
||||||
|
|
||||||
private TreePath[] currentSelectionPaths;
|
private TreePath[] currentSelectionPaths;
|
||||||
private List<TreePath> currentExpandedPaths;
|
private List<TreePath> currentExpandedPaths;
|
||||||
private Point currentViewPosition;
|
private Point currentViewPosition;
|
||||||
|
|
||||||
private Map<String, ObjectNode> nodeMap = new LinkedHashMap<>();
|
|
||||||
private SwingUpdateManager restoreTreeStateManager =
|
|
||||||
new SwingUpdateManager(this::restoreTreeState);
|
|
||||||
|
|
||||||
public ObjectTree(ObjectContainer container) {
|
public ObjectTree(ObjectContainer container) {
|
||||||
this.root = new ObjectNode(this, null, container);
|
this.root = new ObjectNode(this, null, container);
|
||||||
addToMap(null, container, root);
|
addToMap(null, container, root);
|
||||||
this.tree = new GTree(root);
|
this.tree = new MyGTree(root);
|
||||||
|
|
||||||
tree.addGTreeSelectionListener(new GTreeSelectionListener() {
|
tree.addGTreeSelectionListener(new GTreeSelectionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void valueChanged(GTreeSelectionEvent e) {
|
public void valueChanged(GTreeSelectionEvent e) {
|
||||||
DebuggerObjectsProvider provider = container.getProvider();
|
DebuggerObjectsProvider provider = container.getProvider();
|
||||||
provider.updateActions(container);
|
provider.updateActions(container);
|
||||||
TreePath path = e.getPath();
|
|
||||||
Object last = path.getLastPathComponent();
|
|
||||||
if (!(last instanceof ObjectNode)) {
|
|
||||||
throw new RuntimeException("Path terminating in non-ObjectNode");
|
|
||||||
}
|
|
||||||
ObjectNode node = (ObjectNode) last;
|
|
||||||
TargetObject targetObject = node.getTargetObject();
|
|
||||||
if (targetObject != null && !(targetObject instanceof DummyTargetObject) &&
|
|
||||||
e.getEventOrigin().equals(EventOrigin.USER_GENERATED)) {
|
|
||||||
DebugModelConventions.requestActivation(targetObject).exceptionally(ex -> {
|
|
||||||
Msg.error(this, "Could not activate " + targetObject, ex);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
DebugModelConventions.requestFocus(targetObject).exceptionally(ex -> {
|
|
||||||
Msg.error(this, "Could not focus " + targetObject, ex);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
provider.getTool().contextChanged(provider);
|
provider.getTool().contextChanged(provider);
|
||||||
if (e.getEventOrigin() == EventOrigin.INTERNAL_GENERATED) {
|
if (e.getEventOrigin() == EventOrigin.INTERNAL_GENERATED) {
|
||||||
restoreTreeStateManager.updateLater();
|
restoreTreeStateManager.updateLater();
|
||||||
|
@ -119,8 +111,7 @@ public class ObjectTree implements ObjectPane {
|
||||||
});
|
});
|
||||||
tree.setCellRenderer(new ObjectTreeCellRenderer(root.getProvider()));
|
tree.setCellRenderer(new ObjectTreeCellRenderer(root.getProvider()));
|
||||||
tree.setDataTransformer(t -> {
|
tree.setDataTransformer(t -> {
|
||||||
if (t instanceof ObjectNode) {
|
if (t instanceof ObjectNode node) {
|
||||||
ObjectNode node = (ObjectNode) t;
|
|
||||||
return List.of(node.getContainer().getDecoratedName());
|
return List.of(node.getContainer().getDecoratedName());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -131,8 +122,7 @@ public class ObjectTree implements ObjectPane {
|
||||||
public void treeExpanded(TreeExpansionEvent event) {
|
public void treeExpanded(TreeExpansionEvent event) {
|
||||||
TreePath expandedPath = event.getPath();
|
TreePath expandedPath = event.getPath();
|
||||||
Object last = expandedPath.getLastPathComponent();
|
Object last = expandedPath.getLastPathComponent();
|
||||||
if (last instanceof ObjectNode) {
|
if (last instanceof ObjectNode node) {
|
||||||
ObjectNode node = (ObjectNode) last;
|
|
||||||
node.markExpanded();
|
node.markExpanded();
|
||||||
currentExpandedPaths = tree.getExpandedPaths();
|
currentExpandedPaths = tree.getExpandedPaths();
|
||||||
}
|
}
|
||||||
|
@ -142,8 +132,7 @@ public class ObjectTree implements ObjectPane {
|
||||||
public void treeCollapsed(TreeExpansionEvent event) {
|
public void treeCollapsed(TreeExpansionEvent event) {
|
||||||
TreePath collapsedPath = event.getPath();
|
TreePath collapsedPath = event.getPath();
|
||||||
Object last = collapsedPath.getLastPathComponent();
|
Object last = collapsedPath.getLastPathComponent();
|
||||||
if (last instanceof ObjectNode) {
|
if (last instanceof ObjectNode node) {
|
||||||
ObjectNode node = (ObjectNode) last;
|
|
||||||
node.markCollapsed();
|
node.markCollapsed();
|
||||||
currentExpandedPaths = tree.getExpandedPaths();
|
currentExpandedPaths = tree.getExpandedPaths();
|
||||||
}
|
}
|
||||||
|
@ -153,11 +142,17 @@ public class ObjectTree implements ObjectPane {
|
||||||
tree.addMouseListener(new MouseAdapter() {
|
tree.addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(MouseEvent e) {
|
public void mouseClicked(MouseEvent e) {
|
||||||
if (e.getClickCount() == 2) {
|
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||||
TargetObject selectedObject = getSelectedObject();
|
activateOrNavigateSelectedObject();
|
||||||
if (selectedObject != null) {
|
}
|
||||||
container.getProvider().navigateToSelectedObject(selectedObject, null);
|
}
|
||||||
}
|
});
|
||||||
|
tree.tree().addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
|
activateOrNavigateSelectedObject();
|
||||||
|
e.consume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -166,6 +161,27 @@ public class ObjectTree implements ObjectPane {
|
||||||
tree.setSelectedNode(root);
|
tree.setSelectedNode(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void activateOrNavigateSelectedObject() {
|
||||||
|
TargetObject object = getSelectedObject();
|
||||||
|
if (object == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (getProvider().navigateToSelectedObject(object, null) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (object instanceof DummyTargetObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DebugModelConventions.requestActivation(object).exceptionally(ex -> {
|
||||||
|
Msg.error(this, "Could not activate " + object, ex);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
/*DebugModelConventions.requestFocus(object).exceptionally(ex -> {
|
||||||
|
Msg.error(this, "Could not focus " + object, ex);
|
||||||
|
return null;
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObjectContainer getContainer() {
|
public ObjectContainer getContainer() {
|
||||||
return root.getContainer();
|
return root.getContainer();
|
||||||
|
@ -188,8 +204,8 @@ public class ObjectTree implements ObjectPane {
|
||||||
}
|
}
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
Object last = path.getLastPathComponent();
|
Object last = path.getLastPathComponent();
|
||||||
if (last instanceof ObjectNode) {
|
if (last instanceof ObjectNode node) {
|
||||||
return ((ObjectNode) last).getContainer().getTargetObject();
|
return node.getContainer().getTargetObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -303,8 +319,7 @@ public class ObjectTree implements ObjectPane {
|
||||||
for (TreePath path : expandedPaths) {
|
for (TreePath path : expandedPaths) {
|
||||||
Object[] objs = path.getPath();
|
Object[] objs = path.getPath();
|
||||||
for (int i = 0; i < objs.length - 2; i++) {
|
for (int i = 0; i < objs.length - 2; i++) {
|
||||||
if (objs[i] instanceof ObjectNode) {
|
if (objs[i] instanceof ObjectNode node) {
|
||||||
ObjectNode node = (ObjectNode) objs[i];
|
|
||||||
if (!node.isLoaded()) {
|
if (!node.isLoaded()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -364,6 +379,14 @@ public class ObjectTree implements ObjectPane {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelectedObject(TargetObject object) {
|
||||||
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
|
List<String> path = object.getPath();
|
||||||
|
tree.setSelectedNodeByNamePath(addRootNameToPath(path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private String[] addRootNameToPath(List<String> path) {
|
private String[] addRootNameToPath(List<String> path) {
|
||||||
String[] fullPath = new String[path.size() + 1];
|
String[] fullPath = new String[path.size() + 1];
|
||||||
fullPath[0] = tree.getModelRoot().getName();
|
fullPath[0] = tree.getModelRoot().getName();
|
||||||
|
|
|
@ -30,7 +30,6 @@ import ghidra.dbg.target.TargetExecutionStateful;
|
||||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
|
||||||
// TODO: In the new scheme, I'm not sure this is applicable anymore.
|
|
||||||
class ObjectTreeCellRenderer extends GTreeRenderer {
|
class ObjectTreeCellRenderer extends GTreeRenderer {
|
||||||
private static final String FONT_ID = "font.debugger.object.tree.renderer";
|
private static final String FONT_ID = "font.debugger.object.tree.renderer";
|
||||||
|
|
||||||
|
@ -45,12 +44,17 @@ class ObjectTreeCellRenderer extends GTreeRenderer {
|
||||||
boolean leaf, int row, boolean focus) {
|
boolean leaf, int row, boolean focus) {
|
||||||
Component component =
|
Component component =
|
||||||
super.getTreeCellRendererComponent(t, value, sel, exp, leaf, row, focus);
|
super.getTreeCellRendererComponent(t, value, sel, exp, leaf, row, focus);
|
||||||
if (value instanceof ObjectNode) {
|
if (value instanceof ObjectNode node) {
|
||||||
ObjectNode node = (ObjectNode) value;
|
|
||||||
ObjectContainer container = node.getContainer();
|
ObjectContainer container = node.getContainer();
|
||||||
setText(container.getDecoratedName());
|
setText(container.getDecoratedName());
|
||||||
component.setForeground(provider.COLOR_FOREGROUND);
|
component.setForeground(provider.COLOR_FOREGROUND);
|
||||||
TargetObject targetObject = container.getTargetObject();
|
TargetObject targetObject = container.getTargetObject();
|
||||||
|
if (container.isSubscribed()) {
|
||||||
|
Color color = provider.COLOR_FOREGROUND_SUBSCRIBED;
|
||||||
|
if (!color.equals(Tables.FG_UNSELECTED)) {
|
||||||
|
component.setForeground(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (targetObject != null) {
|
if (targetObject != null) {
|
||||||
Map<String, ?> attrs = targetObject.getCachedAttributes();
|
Map<String, ?> attrs = targetObject.getCachedAttributes();
|
||||||
String kind = (String) attrs.get(TargetObject.KIND_ATTRIBUTE_NAME);
|
String kind = (String) attrs.get(TargetObject.KIND_ATTRIBUTE_NAME);
|
||||||
|
@ -76,12 +80,6 @@ class ObjectTreeCellRenderer extends GTreeRenderer {
|
||||||
if (container.isModified()) {
|
if (container.isModified()) {
|
||||||
component.setForeground(provider.COLOR_FOREGROUND_MODIFIED);
|
component.setForeground(provider.COLOR_FOREGROUND_MODIFIED);
|
||||||
}
|
}
|
||||||
if (container.isSubscribed()) {
|
|
||||||
Color color = provider.COLOR_FOREGROUND_SUBSCRIBED;
|
|
||||||
if (!color.equals(Tables.FG_UNSELECTED)) {
|
|
||||||
component.setForeground(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TreePath path = t.getSelectionPath();
|
TreePath path = t.getSelectionPath();
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
Object last = path.getLastPathComponent();
|
Object last = path.getLastPathComponent();
|
||||||
|
@ -93,8 +91,8 @@ class ObjectTreeCellRenderer extends GTreeRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Font font = Gui.getFont(FONT_ID);
|
Font font = Gui.getFont(FONT_ID);
|
||||||
if (container.isSubscribed()) {
|
if (container.isFocused()) {
|
||||||
font = font.deriveFont(Font.ITALIC);
|
font = font.deriveFont(Font.BOLD);
|
||||||
}
|
}
|
||||||
component.setFont(font);
|
component.setFont(font);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1296,7 +1296,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||||
return AsyncUtils.NIL;
|
return AsyncUtils.NIL;
|
||||||
}
|
}
|
||||||
toRead.retainAll(regMapper.getRegistersOnTarget());
|
toRead.retainAll(regMapper.getRegistersOnTarget());
|
||||||
Set<TargetRegisterBank> banks = recorder.getTargetRegisterBanks(traceThread, current.getFrame());
|
Set<TargetRegisterBank> banks =
|
||||||
|
recorder.getTargetRegisterBanks(traceThread, current.getFrame());
|
||||||
if (banks == null || banks.isEmpty()) {
|
if (banks == null || banks.isEmpty()) {
|
||||||
Msg.error(this, "Current frame's bank does not exist");
|
Msg.error(this, "Current frame's bank does not exist");
|
||||||
return AsyncUtils.NIL;
|
return AsyncUtils.NIL;
|
||||||
|
@ -1341,16 +1342,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||||
}
|
}
|
||||||
return future.exceptionally(ex -> {
|
return future.exceptionally(ex -> {
|
||||||
ex = AsyncUtils.unwrapThrowable(ex);
|
ex = AsyncUtils.unwrapThrowable(ex);
|
||||||
if (ex instanceof DebuggerModelAccessException) {
|
String msg = "Could not read target registers for selected thread: " + ex.getMessage();
|
||||||
String msg =
|
Msg.info(this, msg);
|
||||||
"Could not read target registers for selected thread: " + ex.getMessage();
|
plugin.getTool().setStatusInfo(msg);
|
||||||
Msg.info(this, msg);
|
|
||||||
plugin.getTool().setStatusInfo(msg);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Msg.showError(this, getComponent(), "Read Target Registers",
|
|
||||||
"Could not read target registers for selected thread", ex);
|
|
||||||
}
|
|
||||||
return ExceptionUtils.rethrow(ex);
|
return ExceptionUtils.rethrow(ex);
|
||||||
}).thenApply(__ -> null);
|
}).thenApply(__ -> null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,23 +16,20 @@
|
||||||
package ghidra.app.plugin.core.debug.gui.stack;
|
package ghidra.app.plugin.core.debug.gui.stack;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.Component;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.*;
|
import java.util.function.*;
|
||||||
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.table.TableColumn;
|
import javax.swing.table.*;
|
||||||
import javax.swing.table.TableColumnModel;
|
|
||||||
|
|
||||||
import docking.widgets.table.CustomToStringCellRenderer;
|
import docking.widgets.table.*;
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel;
|
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.dbg.DebugModelConventions;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.dbg.target.TargetStackFrame;
|
|
||||||
import ghidra.framework.plugintool.AutoService;
|
import ghidra.framework.plugintool.AutoService;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
|
@ -52,8 +49,7 @@ import ghidra.trace.util.TraceRegisterUtils;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
import utilities.util.SuppressableCallback;
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
import utilities.util.SuppressableCallback.Suppression;
|
|
||||||
|
|
||||||
public class DebuggerLegacyStackPanel extends JPanel {
|
public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
|
|
||||||
|
@ -202,8 +198,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerTraceManagerService traceManager;
|
private DebuggerTraceManagerService traceManager;
|
||||||
// @AutoServiceConsumed by method
|
|
||||||
private DebuggerModelService modelService;
|
|
||||||
// @AutoServiceConsumed via method
|
// @AutoServiceConsumed via method
|
||||||
DebuggerStaticMappingService mappingService;
|
DebuggerStaticMappingService mappingService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
|
@ -213,6 +207,23 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
|
final TableCellRenderer boldCurrentRenderer = new AbstractGColumnRenderer<Object>() {
|
||||||
|
@Override
|
||||||
|
public String getFilterString(Object t, Settings settings) {
|
||||||
|
return t == null ? "<null>" : t.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
super.getTableCellRendererComponent(data);
|
||||||
|
StackFrameRow row = (StackFrameRow) data.getRowObject();
|
||||||
|
if (row != null && row.getFrameLevel() == current.getFrame()) {
|
||||||
|
setBold();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Table rows access this for function name resolution
|
// Table rows access this for function name resolution
|
||||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
private Trace currentTrace; // Copy for transition
|
private Trace currentTrace; // Copy for transition
|
||||||
|
@ -222,8 +233,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
private ForStackListener forStackListener = new ForStackListener();
|
private ForStackListener forStackListener = new ForStackListener();
|
||||||
private ForFunctionsListener forFunctionsListener = new ForFunctionsListener();
|
private ForFunctionsListener forFunctionsListener = new ForFunctionsListener();
|
||||||
|
|
||||||
private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>();
|
|
||||||
|
|
||||||
protected final StackTableModel stackTableModel;
|
protected final StackTableModel stackTableModel;
|
||||||
protected GhidraTable stackTable;
|
protected GhidraTable stackTable;
|
||||||
protected GhidraTableFilterPanel<StackFrameRow> stackFilterPanel;
|
protected GhidraTableFilterPanel<StackFrameRow> stackFilterPanel;
|
||||||
|
@ -246,33 +255,23 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
contextChanged();
|
contextChanged();
|
||||||
activateSelectedFrame();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
stackTable.addMouseListener(new MouseAdapter() {
|
stackTable.addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(MouseEvent e) {
|
public void mouseClicked(MouseEvent e) {
|
||||||
if (e.getClickCount() < 2 || e.getButton() != MouseEvent.BUTTON1) {
|
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||||
return;
|
activateSelectedFrame();
|
||||||
}
|
}
|
||||||
if (listingService == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (myActionContext == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Address pc = myActionContext.getFrame().getProgramCounter();
|
|
||||||
if (pc == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
listingService.goTo(pc, true);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
stackTable.addKeyListener(new KeyAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mouseReleased(MouseEvent e) {
|
public void keyPressed(KeyEvent e) {
|
||||||
int selectedRow = stackTable.getSelectedRow();
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
StackFrameRow row = stackTableModel.getRowObject(selectedRow);
|
activateSelectedFrame();
|
||||||
rowActivated(row);
|
e.consume(); // lest it select the next row down
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -281,8 +280,14 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
|
|
||||||
TableColumn levelCol = columnModel.getColumn(StackTableColumns.LEVEL.ordinal());
|
TableColumn levelCol = columnModel.getColumn(StackTableColumns.LEVEL.ordinal());
|
||||||
levelCol.setPreferredWidth(25);
|
levelCol.setPreferredWidth(25);
|
||||||
TableColumn baseCol = columnModel.getColumn(StackTableColumns.PC.ordinal());
|
levelCol.setCellRenderer(boldCurrentRenderer);
|
||||||
baseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
TableColumn pcCol = columnModel.getColumn(StackTableColumns.PC.ordinal());
|
||||||
|
pcCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||||
|
pcCol.setCellRenderer(boldCurrentRenderer);
|
||||||
|
TableColumn funcCol = columnModel.getColumn(StackTableColumns.FUNCTION.ordinal());
|
||||||
|
funcCol.setCellRenderer(boldCurrentRenderer);
|
||||||
|
TableColumn commCol = columnModel.getColumn(StackTableColumns.COMMENT.ordinal());
|
||||||
|
commCol.setCellRenderer(boldCurrentRenderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void contextChanged() {
|
protected void contextChanged() {
|
||||||
|
@ -293,37 +298,13 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void activateSelectedFrame() {
|
protected void activateSelectedFrame() {
|
||||||
// TODO: Action to toggle sync?
|
|
||||||
if (myActionContext == null) {
|
if (myActionContext == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (traceManager == null) {
|
if (traceManager == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cbFrameSelected.invoke(() -> {
|
traceManager.activateFrame(myActionContext.getFrame().getFrameLevel());
|
||||||
traceManager.activateFrame(myActionContext.getFrame().getFrameLevel());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rowActivated(StackFrameRow row) {
|
|
||||||
if (row == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceStackFrame frame = row.frame;
|
|
||||||
if (frame == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceThread thread = frame.getStack().getThread();
|
|
||||||
Trace trace = thread.getTrace();
|
|
||||||
TraceRecorder recorder = modelService.getRecorder(trace);
|
|
||||||
if (recorder == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TargetStackFrame targetFrame = recorder.getTargetStackFrame(thread, frame.getLevel());
|
|
||||||
if (targetFrame == null || !targetFrame.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DebugModelConventions.requestActivation(targetFrame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateStack() {
|
protected void updateStack() {
|
||||||
|
@ -344,8 +325,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
stackTableModel.fireTableDataChanged();
|
stackTableModel.fireTableDataChanged();
|
||||||
|
|
||||||
selectCurrentFrame();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doSetCurrentStack(TraceStack stack) {
|
protected void doSetCurrentStack(TraceStack stack) {
|
||||||
|
@ -411,7 +390,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
else {
|
else {
|
||||||
doSetCurrentStack(stack);
|
doSetCurrentStack(stack);
|
||||||
}
|
}
|
||||||
selectCurrentFrame();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeOldListeners() {
|
private void removeOldListeners() {
|
||||||
|
@ -441,19 +419,17 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
doSetTrace(current.getTrace());
|
doSetTrace(current.getTrace());
|
||||||
loadStack();
|
loadStack();
|
||||||
|
selectCurrentFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void selectCurrentFrame() {
|
protected void selectCurrentFrame() {
|
||||||
try (Suppression supp = cbFrameSelected.suppress(null)) {
|
StackFrameRow row = stackTableModel.findFirst(r -> r.getFrameLevel() == current.getFrame());
|
||||||
StackFrameRow row =
|
if (row == null) {
|
||||||
stackTableModel.findFirst(r -> r.getFrameLevel() == current.getFrame());
|
// Strange
|
||||||
if (row == null) {
|
stackTable.clearSelection();
|
||||||
// Strange
|
}
|
||||||
stackTable.clearSelection();
|
else {
|
||||||
}
|
stackFilterPanel.setSelectedItem(row);
|
||||||
else {
|
|
||||||
stackFilterPanel.setSelectedItem(row);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,11 +437,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
return myActionContext;
|
return myActionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoServiceConsumed
|
|
||||||
public void setModelService(DebuggerModelService modelService) {
|
|
||||||
this.modelService = modelService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private void setMappingService(DebuggerStaticMappingService mappingService) {
|
private void setMappingService(DebuggerStaticMappingService mappingService) {
|
||||||
if (this.mappingService != null) {
|
if (this.mappingService != null) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ package ghidra.app.plugin.core.debug.gui.stack;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.event.ListSelectionEvent;
|
import javax.swing.JTable;
|
||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
|
|
||||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||||
|
@ -43,8 +43,6 @@ import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.stack.TraceObjectStackFrame;
|
import ghidra.trace.model.stack.TraceObjectStackFrame;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.target.TraceObjectValue;
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
import utilities.util.SuppressableCallback;
|
|
||||||
import utilities.util.SuppressableCallback.Suppression;
|
|
||||||
|
|
||||||
public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObjectStackFrame>
|
public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObjectStackFrame>
|
||||||
implements ListSelectionListener, CellActivationListener {
|
implements ListSelectionListener, CellActivationListener {
|
||||||
|
@ -109,8 +107,6 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObje
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
protected DebuggerTraceManagerService traceManager;
|
protected DebuggerTraceManagerService traceManager;
|
||||||
|
|
||||||
private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>();
|
|
||||||
|
|
||||||
public DebuggerStackPanel(DebuggerStackProvider provider) {
|
public DebuggerStackPanel(DebuggerStackProvider provider) {
|
||||||
super(provider.plugin, provider, TraceObjectStackFrame.class);
|
super(provider.plugin, provider, TraceObjectStackFrame.class);
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
|
@ -139,21 +135,16 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObje
|
||||||
super.coordinatesActivated(coordinates);
|
super.coordinatesActivated(coordinates);
|
||||||
TraceObject object = coordinates.getObject();
|
TraceObject object = coordinates.getObject();
|
||||||
if (object != null) {
|
if (object != null) {
|
||||||
try (Suppression supp = cbFrameSelected.suppress(null)) {
|
trySelectAncestor(object);
|
||||||
trySelectAncestor(object);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void valueChanged(ListSelectionEvent e) {
|
public void cellActivated(JTable table) {
|
||||||
super.valueChanged(e);
|
// No super
|
||||||
if (e.getValueIsAdjusting()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ValueRow item = getSelectedItem();
|
ValueRow item = getSelectedItem();
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
cbFrameSelected.invoke(() -> traceManager.activateObject(item.getValue().getChild()));
|
traceManager.activateObject(item.getValue().getChild());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,14 @@
|
||||||
package ghidra.app.plugin.core.debug.gui.thread;
|
package ghidra.app.plugin.core.debug.gui.thread;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Component;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.ListSelectionEvent;
|
import javax.swing.event.ListSelectionEvent;
|
||||||
import javax.swing.table.TableColumn;
|
import javax.swing.table.*;
|
||||||
import javax.swing.table.TableColumnModel;
|
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
|
@ -32,6 +32,7 @@ import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.model.DomainObject;
|
import ghidra.framework.model.DomainObject;
|
||||||
import ghidra.framework.model.DomainObjectChangeRecord;
|
import ghidra.framework.model.DomainObjectChangeRecord;
|
||||||
import ghidra.framework.plugintool.AutoService;
|
import ghidra.framework.plugintool.AutoService;
|
||||||
|
@ -45,8 +46,7 @@ import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.util.database.ObjectKey;
|
import ghidra.util.database.ObjectKey;
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
import utilities.util.SuppressableCallback;
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
import utilities.util.SuppressableCallback.Suppression;
|
|
||||||
|
|
||||||
public class DebuggerLegacyThreadsPanel extends JPanel {
|
public class DebuggerLegacyThreadsPanel extends JPanel {
|
||||||
|
|
||||||
|
@ -175,13 +175,28 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
|
||||||
|
|
||||||
private final ForThreadsListener forThreadsListener = new ForThreadsListener();
|
private final ForThreadsListener forThreadsListener = new ForThreadsListener();
|
||||||
|
|
||||||
private final SuppressableCallback<Void> cbCoordinateActivation = new SuppressableCallback<>();
|
|
||||||
|
|
||||||
/* package access for testing */
|
/* package access for testing */
|
||||||
final SpanTableCellRenderer<Long> spanRenderer = new SpanTableCellRenderer<>();
|
final SpanTableCellRenderer<Long> spanRenderer = new SpanTableCellRenderer<>();
|
||||||
final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
||||||
new RangeCursorTableHeaderRenderer<>(0L);
|
new RangeCursorTableHeaderRenderer<>(0L);
|
||||||
|
|
||||||
|
final TableCellRenderer boldCurrentRenderer = new AbstractGColumnRenderer<Object>() {
|
||||||
|
@Override
|
||||||
|
public String getFilterString(Object t, Settings settings) {
|
||||||
|
return t == null ? "<null>" : t.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
super.getTableCellRendererComponent(data);
|
||||||
|
ThreadRow row = (ThreadRow) data.getRowObject();
|
||||||
|
if (row != null && row.getThread() == current.getThread()) {
|
||||||
|
setBold();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
final ThreadTableModel threadTableModel;
|
final ThreadTableModel threadTableModel;
|
||||||
final GTable threadTable;
|
final GTable threadTable;
|
||||||
final GhidraTableFilterPanel<ThreadRow> threadFilterPanel;
|
final GhidraTableFilterPanel<ThreadRow> threadFilterPanel;
|
||||||
|
@ -208,6 +223,23 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
|
||||||
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
|
||||||
|
|
||||||
threadTable.getSelectionModel().addListSelectionListener(this::threadRowSelected);
|
threadTable.getSelectionModel().addListSelectionListener(this::threadRowSelected);
|
||||||
|
threadTable.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||||
|
activateSelectedThread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
threadTable.addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
|
activateSelectedThread();
|
||||||
|
e.consume(); // lest it select the next row down
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
threadTable.addFocusListener(new FocusAdapter() {
|
threadTable.addFocusListener(new FocusAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void focusGained(FocusEvent e) {
|
public void focusGained(FocusEvent e) {
|
||||||
|
@ -224,14 +256,19 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
|
||||||
TableColumnModel columnModel = threadTable.getColumnModel();
|
TableColumnModel columnModel = threadTable.getColumnModel();
|
||||||
TableColumn colName = columnModel.getColumn(ThreadTableColumns.NAME.ordinal());
|
TableColumn colName = columnModel.getColumn(ThreadTableColumns.NAME.ordinal());
|
||||||
colName.setPreferredWidth(100);
|
colName.setPreferredWidth(100);
|
||||||
|
colName.setCellRenderer(boldCurrentRenderer);
|
||||||
TableColumn colCreated = columnModel.getColumn(ThreadTableColumns.CREATED.ordinal());
|
TableColumn colCreated = columnModel.getColumn(ThreadTableColumns.CREATED.ordinal());
|
||||||
colCreated.setPreferredWidth(10);
|
colCreated.setPreferredWidth(10);
|
||||||
|
colCreated.setCellRenderer(boldCurrentRenderer);
|
||||||
TableColumn colDestroyed = columnModel.getColumn(ThreadTableColumns.DESTROYED.ordinal());
|
TableColumn colDestroyed = columnModel.getColumn(ThreadTableColumns.DESTROYED.ordinal());
|
||||||
colDestroyed.setPreferredWidth(10);
|
colDestroyed.setPreferredWidth(10);
|
||||||
|
colDestroyed.setCellRenderer(boldCurrentRenderer);
|
||||||
TableColumn colState = columnModel.getColumn(ThreadTableColumns.STATE.ordinal());
|
TableColumn colState = columnModel.getColumn(ThreadTableColumns.STATE.ordinal());
|
||||||
colState.setPreferredWidth(20);
|
colState.setPreferredWidth(20);
|
||||||
|
colState.setCellRenderer(boldCurrentRenderer);
|
||||||
TableColumn colComment = columnModel.getColumn(ThreadTableColumns.COMMENT.ordinal());
|
TableColumn colComment = columnModel.getColumn(ThreadTableColumns.COMMENT.ordinal());
|
||||||
colComment.setPreferredWidth(100);
|
colComment.setPreferredWidth(100);
|
||||||
|
colComment.setCellRenderer(boldCurrentRenderer);
|
||||||
TableColumn colPlot = columnModel.getColumn(ThreadTableColumns.PLOT.ordinal());
|
TableColumn colPlot = columnModel.getColumn(ThreadTableColumns.PLOT.ordinal());
|
||||||
colPlot.setPreferredWidth(200);
|
colPlot.setPreferredWidth(200);
|
||||||
colPlot.setCellRenderer(spanRenderer);
|
colPlot.setCellRenderer(spanRenderer);
|
||||||
|
@ -281,19 +318,13 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doSetThread(TraceThread thread) {
|
private void doSetThread(TraceThread thread) {
|
||||||
ThreadRow row = threadFilterPanel.getSelectedItem();
|
if (thread != null) {
|
||||||
TraceThread curThread = row == null ? null : row.getThread();
|
threadFilterPanel.setSelectedItem(threadTableModel.getRow(thread));
|
||||||
if (curThread == thread) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
else {
|
||||||
if (thread != null) {
|
threadTable.clearSelection();
|
||||||
threadFilterPanel.setSelectedItem(threadTableModel.getRow(thread));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
threadTable.clearSelection();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
threadTableModel.fireTableDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doSetSnap(long snap) {
|
private void doSetSnap(long snap) {
|
||||||
|
@ -324,9 +355,13 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
|
||||||
if (e.getValueIsAdjusting()) {
|
if (e.getValueIsAdjusting()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setThreadRowActionContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void activateSelectedThread() {
|
||||||
ThreadRow row = setThreadRowActionContext();
|
ThreadRow row = setThreadRowActionContext();
|
||||||
if (row != null && traceManager != null) {
|
if (row != null && traceManager != null) {
|
||||||
cbCoordinateActivation.invoke(() -> traceManager.activateThread(row.getThread()));
|
traceManager.activateThread(row.getThread());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,10 @@ package ghidra.app.plugin.core.debug.gui.thread;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.event.ListSelectionEvent;
|
import javax.swing.JTable;
|
||||||
|
|
||||||
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
|
import docking.widgets.table.threaded.ThreadedTableModelListener;
|
||||||
import docking.widgets.table.TableColumnDescriptor;
|
import docking.widgets.table.TableColumnDescriptor;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.*;
|
import ghidra.app.plugin.core.debug.gui.model.*;
|
||||||
|
@ -37,8 +38,6 @@ import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.target.TraceObjectValue;
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
import ghidra.trace.model.thread.TraceObjectThread;
|
import ghidra.trace.model.thread.TraceObjectThread;
|
||||||
import utilities.util.SuppressableCallback;
|
|
||||||
import utilities.util.SuppressableCallback.Suppression;
|
|
||||||
|
|
||||||
public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceObjectThread> {
|
public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceObjectThread> {
|
||||||
|
|
||||||
|
@ -166,8 +165,6 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
protected DebuggerTraceManagerService traceManager;
|
protected DebuggerTraceManagerService traceManager;
|
||||||
|
|
||||||
private final SuppressableCallback<Void> cbThreadSelected = new SuppressableCallback<>();
|
|
||||||
|
|
||||||
private final SeekListener seekListener = pos -> {
|
private final SeekListener seekListener = pos -> {
|
||||||
long snap = Math.round(pos);
|
long snap = Math.round(pos);
|
||||||
if (current.getTrace() == null || snap < 0) {
|
if (current.getTrace() == null || snap < 0) {
|
||||||
|
@ -180,11 +177,22 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||||
super(provider.plugin, provider, TraceObjectThread.class);
|
super(provider.plugin, provider, TraceObjectThread.class);
|
||||||
setLimitToSnap(false); // TODO: Toggle for this?
|
setLimitToSnap(false); // TODO: Toggle for this?
|
||||||
|
|
||||||
tableModel.addTableModelListener(e -> {
|
|
||||||
// This seems a bit heavy handed
|
|
||||||
trySelectCurrentThread();
|
|
||||||
});
|
|
||||||
addSeekListener(seekListener);
|
addSeekListener(seekListener);
|
||||||
|
|
||||||
|
tableModel.addThreadedTableModelListener(new ThreadedTableModelListener() {
|
||||||
|
@Override
|
||||||
|
public void loadingStarted() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadingFinished(boolean wasCancelled) {
|
||||||
|
trySelectCurrentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadPending() {
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -211,11 +219,10 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||||
|
|
||||||
private void trySelectCurrentThread() {
|
private void trySelectCurrentThread() {
|
||||||
TraceObject object = current.getObject();
|
TraceObject object = current.getObject();
|
||||||
if (object != null) {
|
if (object == null) {
|
||||||
try (Suppression supp = cbThreadSelected.suppress(null)) {
|
return;
|
||||||
trySelectAncestor(object);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
trySelectAncestor(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -225,21 +232,11 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void valueChanged(ListSelectionEvent e) {
|
public void cellActivated(JTable table) {
|
||||||
super.valueChanged(e);
|
// No super
|
||||||
if (e.getValueIsAdjusting()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ValueRow item = getSelectedItem();
|
ValueRow item = getSelectedItem();
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
cbThreadSelected.invoke(() -> {
|
traceManager.activateObject(item.getValue().getChild());
|
||||||
if (current.getTrace() != item.getValue().getTrace()) {
|
|
||||||
// Prevent timing issues during navigation from causing trace changes
|
|
||||||
// Thread table should never cause trace change anyway
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
traceManager.activateObject(item.getValue().getChild());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import docking.action.*;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SynchronizeFocusAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SynchronizeTargetAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.ToToggleSelectionListener;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.ToToggleSelectionListener;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
|
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
|
||||||
|
@ -106,8 +106,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
private final BooleanChangeAdapter synchronizeFocusChangeListener =
|
private final BooleanChangeAdapter synchronizeTargetChangeListener =
|
||||||
this::changedSynchronizeFocus;
|
this::changedSynchronizeTarget;
|
||||||
|
|
||||||
private final ForSnapsListener forSnapsListener = new ForSnapsListener();
|
private final ForSnapsListener forSnapsListener = new ForSnapsListener();
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
DebuggerLegacyThreadsPanel legacyPanel;
|
DebuggerLegacyThreadsPanel legacyPanel;
|
||||||
|
|
||||||
DockingAction actionSaveTrace;
|
DockingAction actionSaveTrace;
|
||||||
ToggleDockingAction actionSyncFocus;
|
ToggleDockingAction actionSyncTarget;
|
||||||
|
|
||||||
ActionContext myActionContext;
|
ActionContext myActionContext;
|
||||||
|
|
||||||
|
@ -149,13 +149,13 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
public void setTraceManager(DebuggerTraceManagerService traceManager) {
|
public void setTraceManager(DebuggerTraceManagerService traceManager) {
|
||||||
if (this.traceManager != null) {
|
if (this.traceManager != null) {
|
||||||
this.traceManager.removeSynchronizeFocusChangeListener(synchronizeFocusChangeListener);
|
this.traceManager.removeSynchronizeActiveChangeListener(synchronizeTargetChangeListener);
|
||||||
}
|
}
|
||||||
this.traceManager = traceManager;
|
this.traceManager = traceManager;
|
||||||
if (traceManager != null) {
|
if (traceManager != null) {
|
||||||
traceManager.addSynchronizeFocusChangeListener(synchronizeFocusChangeListener);
|
traceManager.addSynchronizeActiveChangeListener(synchronizeTargetChangeListener);
|
||||||
if (actionSyncFocus != null) {
|
if (actionSyncTarget != null) {
|
||||||
actionSyncFocus.setSelected(traceManager.isSynchronizeFocus());
|
actionSyncTarget.setSelected(traceManager.isSynchronizeActive());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contextChanged();
|
contextChanged();
|
||||||
|
@ -236,27 +236,27 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
actionSyncFocus = SynchronizeFocusAction.builder(plugin)
|
actionSyncTarget = SynchronizeTargetAction.builder(plugin)
|
||||||
.selected(traceManager != null && traceManager.isSynchronizeFocus())
|
.selected(traceManager != null && traceManager.isSynchronizeActive())
|
||||||
.enabledWhen(c -> traceManager != null)
|
.enabledWhen(c -> traceManager != null)
|
||||||
.onAction(c -> toggleSyncFocus(actionSyncFocus.isSelected()))
|
.onAction(c -> toggleSyncFocus(actionSyncTarget.isSelected()))
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
traceManager.addSynchronizeFocusChangeListener(toToggleSelectionListener =
|
traceManager.addSynchronizeActiveChangeListener(toToggleSelectionListener =
|
||||||
new ToToggleSelectionListener(actionSyncFocus));
|
new ToToggleSelectionListener(actionSyncTarget));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changedSynchronizeFocus(boolean value) {
|
private void changedSynchronizeTarget(boolean value) {
|
||||||
if (actionSyncFocus == null || actionSyncFocus.isSelected()) {
|
if (actionSyncTarget == null || actionSyncTarget.isSelected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
actionSyncFocus.setSelected(value);
|
actionSyncTarget.setSelected(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleSyncFocus(boolean enabled) {
|
private void toggleSyncFocus(boolean enabled) {
|
||||||
if (traceManager == null) {
|
if (traceManager == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
traceManager.setSynchronizeFocus(enabled);
|
traceManager.setSynchronizeActive(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,19 +16,20 @@
|
||||||
package ghidra.app.plugin.core.debug.gui.time;
|
package ghidra.app.plugin.core.debug.gui.time;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Component;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.table.TableColumn;
|
import javax.swing.table.*;
|
||||||
import javax.swing.table.TableColumnModel;
|
|
||||||
|
|
||||||
import com.google.common.collect.Collections2;
|
import com.google.common.collect.Collections2;
|
||||||
|
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.model.DomainObject;
|
import ghidra.framework.model.DomainObject;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
@ -37,6 +38,7 @@ import ghidra.trace.model.TraceDomainObjectListener;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.trace.model.time.TraceTimeManager;
|
import ghidra.trace.model.time.TraceTimeManager;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
|
|
||||||
public class DebuggerSnapshotTablePanel extends JPanel {
|
public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
|
|
||||||
|
@ -131,6 +133,23 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final TableCellRenderer boldCurrentRenderer = new AbstractGColumnRenderer<Object>() {
|
||||||
|
@Override
|
||||||
|
public String getFilterString(Object t, Settings settings) {
|
||||||
|
return t == null ? "<null>" : t.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
super.getTableCellRendererComponent(data);
|
||||||
|
SnapshotRow row = (SnapshotRow) data.getRowObject();
|
||||||
|
if (row != null && row.getSnap() == currentSnap) {
|
||||||
|
setBold();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel;
|
protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel;
|
||||||
protected final GTable snapshotTable;
|
protected final GTable snapshotTable;
|
||||||
protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
|
protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
|
||||||
|
@ -155,14 +174,19 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
TableColumnModel columnModel = snapshotTable.getColumnModel();
|
TableColumnModel columnModel = snapshotTable.getColumnModel();
|
||||||
TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal());
|
TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal());
|
||||||
snapCol.setPreferredWidth(40);
|
snapCol.setPreferredWidth(40);
|
||||||
|
snapCol.setCellRenderer(boldCurrentRenderer);
|
||||||
TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal());
|
TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal());
|
||||||
timeCol.setPreferredWidth(200);
|
timeCol.setPreferredWidth(200);
|
||||||
|
timeCol.setCellRenderer(boldCurrentRenderer);
|
||||||
TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
|
TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
|
||||||
etCol.setPreferredWidth(40);
|
etCol.setPreferredWidth(40);
|
||||||
|
etCol.setCellRenderer(boldCurrentRenderer);
|
||||||
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
|
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
|
||||||
schdCol.setPreferredWidth(60);
|
schdCol.setPreferredWidth(60);
|
||||||
|
schdCol.setCellRenderer(boldCurrentRenderer);
|
||||||
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
|
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
|
||||||
descCol.setPreferredWidth(200);
|
descCol.setPreferredWidth(200);
|
||||||
|
descCol.setCellRenderer(boldCurrentRenderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addNewListeners() {
|
private void addNewListeners() {
|
||||||
|
@ -260,5 +284,6 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
snapshotFilterPanel.setSelectedItem(row);
|
snapshotFilterPanel.setSelectedItem(row);
|
||||||
|
snapshotTableModel.fireTableDataChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ package ghidra.app.plugin.core.debug.gui.time;
|
||||||
|
|
||||||
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
|
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.*;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ -146,9 +146,32 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
|
||||||
traceManager.activateSnap(snap);
|
|
||||||
contextChanged();
|
contextChanged();
|
||||||
});
|
});
|
||||||
|
mainPanel.snapshotTable.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||||
|
activateSelectedSnapshot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mainPanel.snapshotTable.addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
|
activateSelectedSnapshot();
|
||||||
|
e.consume(); // lest it select the next row down
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void activateSelectedSnapshot() {
|
||||||
|
Long snap = mainPanel.getSelectedSnapshot();
|
||||||
|
if (snap != null && traceManager != null) {
|
||||||
|
traceManager.activateSnap(snap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
|
|
|
@ -387,15 +387,33 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
||||||
return findFocusScope() != null;
|
return findFocusScope() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupportsActivation() {
|
||||||
|
return findActiveScope() != null;
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: This may require the scope to be an ancestor of the target
|
// NOTE: This may require the scope to be an ancestor of the target
|
||||||
// That should be fine
|
// That should be fine
|
||||||
protected TargetFocusScope findFocusScope() {
|
protected TargetFocusScope findFocusScope() {
|
||||||
List<String> path = target.getModel()
|
List<String> path = target.getModel()
|
||||||
.getRootSchema()
|
.getRootSchema()
|
||||||
.searchForSuitable(TargetFocusScope.class, target.getPath());
|
.searchForSuitable(TargetFocusScope.class, target.getPath());
|
||||||
|
if (path == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (TargetFocusScope) target.getModel().getModelObject(path);
|
return (TargetFocusScope) target.getModel().getModelObject(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TargetActiveScope findActiveScope() {
|
||||||
|
List<String> path = target.getModel()
|
||||||
|
.getRootSchema()
|
||||||
|
.searchForSuitable(TargetActiveScope.class, target.getPath());
|
||||||
|
if (path == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (TargetActiveScope) target.getModel().getModelObject(path);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TargetObject getFocus() {
|
public TargetObject getFocus() {
|
||||||
if (curFocus == null) {
|
if (curFocus == null) {
|
||||||
|
@ -419,8 +437,8 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Boolean> requestFocus(TargetObject focus) {
|
public CompletableFuture<Boolean> requestFocus(TargetObject focus) {
|
||||||
if (!isSupportsFocus()) {
|
if (!isSupportsFocus()) {
|
||||||
return CompletableFuture
|
return CompletableFuture.failedFuture(
|
||||||
.failedFuture(new IllegalArgumentException("Target does not support focus"));
|
new IllegalArgumentException("Target does not support focus"));
|
||||||
}
|
}
|
||||||
if (!PathUtils.isAncestor(getTarget().getPath(), focus.getPath())) {
|
if (!PathUtils.isAncestor(getTarget().getPath(), focus.getPath())) {
|
||||||
return CompletableFuture.failedFuture(new IllegalArgumentException(
|
return CompletableFuture.failedFuture(new IllegalArgumentException(
|
||||||
|
@ -446,6 +464,36 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Boolean> requestActivation(TargetObject active) {
|
||||||
|
if (!isSupportsActivation()) {
|
||||||
|
return CompletableFuture.failedFuture(
|
||||||
|
new IllegalArgumentException("Target does not support activation"));
|
||||||
|
}
|
||||||
|
if (!PathUtils.isAncestor(getTarget().getPath(), active.getPath())) {
|
||||||
|
return CompletableFuture.failedFuture(new IllegalArgumentException(
|
||||||
|
"Requested activation path is not a successor of the target"));
|
||||||
|
}
|
||||||
|
TargetActiveScope activeScope = findActiveScope();
|
||||||
|
if (!PathUtils.isAncestor(activeScope.getPath(), active.getPath())) {
|
||||||
|
// This should be rare, if not forbidden
|
||||||
|
return CompletableFuture.failedFuture(new IllegalArgumentException(
|
||||||
|
"Requested activation path is not a successor of the focus scope"));
|
||||||
|
}
|
||||||
|
return activeScope.requestActivation(active).thenApply(__ -> true).exceptionally(ex -> {
|
||||||
|
ex = AsyncUtils.unwrapThrowable(ex);
|
||||||
|
String msg = "Could not activate " + active + ": " + ex.getMessage();
|
||||||
|
plugin.getTool().setStatusInfo(msg);
|
||||||
|
if (ex instanceof DebuggerModelAccessException) {
|
||||||
|
Msg.info(this, msg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Msg.error(this, "Could not activate " + active, ex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/*---------------- ACCESSOR METHODS -------------------*/
|
/*---------------- ACCESSOR METHODS -------------------*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -737,6 +737,11 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
||||||
return objectRecorder.isSupportsFocus;
|
return objectRecorder.isSupportsFocus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupportsActivation() {
|
||||||
|
return objectRecorder.isSupportsActivation;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TargetObject getFocus() {
|
public TargetObject getFocus() {
|
||||||
return curFocus;
|
return curFocus;
|
||||||
|
@ -764,6 +769,28 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
||||||
return CompletableFuture.completedFuture(false);
|
return CompletableFuture.completedFuture(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Boolean> requestActivation(TargetObject active) {
|
||||||
|
for (TargetActiveScope scope : objectRecorder.collectTargetSuccessors(target,
|
||||||
|
TargetActiveScope.class)) {
|
||||||
|
if (PathUtils.isAncestor(scope.getPath(), active.getPath())) {
|
||||||
|
return scope.requestActivation(active).thenApply(__ -> true).exceptionally(ex -> {
|
||||||
|
ex = AsyncUtils.unwrapThrowable(ex);
|
||||||
|
String msg = "Could not activate " + active + ": " + ex.getMessage();
|
||||||
|
if (ex instanceof DebuggerModelAccessException) {
|
||||||
|
Msg.info(this, msg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Msg.error(this, msg, ex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Msg.info(this, "Could not find suitable active scope for " + active);
|
||||||
|
return CompletableFuture.completedFuture(false);
|
||||||
|
}
|
||||||
|
|
||||||
// UNUSED?
|
// UNUSED?
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> flushTransactions() {
|
public CompletableFuture<Void> flushTransactions() {
|
||||||
|
|
|
@ -26,14 +26,13 @@ import com.google.gson.JsonElement;
|
||||||
|
|
||||||
import ghidra.dbg.DebuggerObjectModel;
|
import ghidra.dbg.DebuggerObjectModel;
|
||||||
import ghidra.dbg.attributes.TargetDataType;
|
import ghidra.dbg.attributes.TargetDataType;
|
||||||
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKind;
|
import ghidra.dbg.target.TargetAttacher.TargetAttachKind;
|
||||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
|
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
|
||||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||||
import ghidra.dbg.target.TargetFocusScope;
|
|
||||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||||
import ghidra.dbg.target.TargetObject;
|
|
||||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||||
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
|
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
|
@ -53,6 +52,7 @@ class ObjectRecorder {
|
||||||
protected final ObjectBasedTraceRecorder recorder;
|
protected final ObjectBasedTraceRecorder recorder;
|
||||||
protected final TraceObjectManager objectManager;
|
protected final TraceObjectManager objectManager;
|
||||||
protected final boolean isSupportsFocus;
|
protected final boolean isSupportsFocus;
|
||||||
|
protected final boolean isSupportsActivation;
|
||||||
|
|
||||||
private final BidiMap<IDKeyed<TargetObject>, IDKeyed<TraceObject>> objectMap =
|
private final BidiMap<IDKeyed<TargetObject>, IDKeyed<TraceObject>> objectMap =
|
||||||
new DualHashBidiMap<>();
|
new DualHashBidiMap<>();
|
||||||
|
@ -62,6 +62,7 @@ class ObjectRecorder {
|
||||||
this.objectManager = recorder.trace.getObjectManager();
|
this.objectManager = recorder.trace.getObjectManager();
|
||||||
TargetObjectSchema schema = recorder.target.getSchema();
|
TargetObjectSchema schema = recorder.target.getSchema();
|
||||||
this.isSupportsFocus = !schema.searchFor(TargetFocusScope.class, false).isEmpty();
|
this.isSupportsFocus = !schema.searchFor(TargetFocusScope.class, false).isEmpty();
|
||||||
|
this.isSupportsActivation = !schema.searchFor(TargetActiveScope.class, false).isEmpty();
|
||||||
|
|
||||||
try (UndoableTransaction tid =
|
try (UndoableTransaction tid =
|
||||||
UndoableTransaction.start(recorder.trace, "Create root")) {
|
UndoableTransaction.start(recorder.trace, "Create root")) {
|
||||||
|
|
|
@ -67,8 +67,8 @@ import ghidra.util.exception.*;
|
||||||
import ghidra.util.task.*;
|
import ghidra.util.task.*;
|
||||||
|
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
shortDescription = "Debugger Trace View Management Plugin",
|
shortDescription = "Debugger Trace Management Plugin",
|
||||||
description = "Manages UI Components, Wrappers, Focus, etc.",
|
description = "Manages the set of open traces, current views, etc.",
|
||||||
category = PluginCategoryNames.DEBUGGER,
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
packageName = DebuggerPluginPackage.NAME,
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.RELEASED,
|
status = PluginStatus.RELEASED,
|
||||||
|
@ -244,8 +244,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
if (curRecorder == null) {
|
if (curRecorder == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: Also re-sync focused thread/frame?
|
DebuggerCoordinates coords = current;
|
||||||
activateNoFocus(current.snap(curRecorder.getSnap()), ActivationCause.FOLLOW_PRESENT);
|
TargetObject focus = curRecorder.getFocus();
|
||||||
|
if (focus != null && synchronizeActive.get()) {
|
||||||
|
coords = coords.object(focus);
|
||||||
|
}
|
||||||
|
coords = coords.snap(curRecorder.getSnap());
|
||||||
|
activateAndNotify(coords, ActivationCause.FOLLOW_PRESENT, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +267,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
||||||
protected final AsyncReference<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true);
|
protected final AsyncReference<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true);
|
||||||
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
||||||
protected final AsyncReference<Boolean, Void> synchronizeFocus = new AsyncReference<>(true);
|
protected final AsyncReference<Boolean, Void> synchronizeActive = new AsyncReference<>(true);
|
||||||
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
||||||
protected final AsyncReference<Boolean, Void> autoCloseOnTerminate = new AsyncReference<>(true);
|
protected final AsyncReference<Boolean, Void> autoCloseOnTerminate = new AsyncReference<>(true);
|
||||||
|
|
||||||
|
@ -585,7 +590,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
|
|
||||||
protected boolean doModelObjectFocused(TargetObject obj, boolean requirePresent) {
|
protected boolean doModelObjectFocused(TargetObject obj, boolean requirePresent) {
|
||||||
curObj = obj;
|
curObj = obj;
|
||||||
if (!synchronizeFocus.get()) {
|
if (!synchronizeActive.get()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (requirePresent && !current.isDeadOrPresent()) {
|
if (requirePresent && !current.isDeadOrPresent()) {
|
||||||
|
@ -612,7 +617,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activateNoFocus(getCurrentFor(trace).object(obj), ActivationCause.SYNC_MODEL);
|
activateAndNotify(getCurrentFor(trace).object(obj), ActivationCause.SYNC_MODEL, false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1086,14 +1091,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return elem.toString();
|
return elem.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void activateNoFocus(DebuggerCoordinates coordinates, ActivationCause cause) {
|
|
||||||
DebuggerCoordinates resolved = doSetCurrent(coordinates, cause);
|
|
||||||
if (resolved == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
prepareViewAndFireEvent(resolved, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static boolean isSameFocus(DebuggerCoordinates prev, DebuggerCoordinates resolved) {
|
protected static boolean isSameFocus(DebuggerCoordinates prev, DebuggerCoordinates resolved) {
|
||||||
if (!Objects.equals(prev.getObject(), resolved.getObject())) {
|
if (!Objects.equals(prev.getObject(), resolved.getObject())) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1110,7 +1107,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static TargetObject translateToFocus(DebuggerCoordinates prev,
|
protected static TargetObject translateToTarget(DebuggerCoordinates prev,
|
||||||
DebuggerCoordinates resolved) {
|
DebuggerCoordinates resolved) {
|
||||||
if (!resolved.isAliveAndPresent()) {
|
if (!resolved.isAliveAndPresent()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1141,7 +1138,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
|
public CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
|
||||||
ActivationCause cause, boolean syncTargetFocus) {
|
ActivationCause cause, boolean syncTarget) {
|
||||||
DebuggerCoordinates prev;
|
DebuggerCoordinates prev;
|
||||||
DebuggerCoordinates resolved;
|
DebuggerCoordinates resolved;
|
||||||
|
|
||||||
|
@ -1158,21 +1155,21 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return AsyncUtils.NIL;
|
return AsyncUtils.NIL;
|
||||||
}
|
}
|
||||||
CompletableFuture<Void> future = prepareViewAndFireEvent(resolved, cause);
|
CompletableFuture<Void> future = prepareViewAndFireEvent(resolved, cause);
|
||||||
if (!syncTargetFocus) {
|
if (!syncTarget) {
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
if (!synchronizeFocus.get()) {
|
if (!synchronizeActive.get()) {
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
TraceRecorder recorder = resolved.getRecorder();
|
TraceRecorder recorder = resolved.getRecorder();
|
||||||
if (recorder == null) {
|
if (recorder == null) {
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
TargetObject focus = translateToFocus(prev, resolved);
|
TargetObject activate = translateToTarget(prev, resolved);
|
||||||
if (focus == null || !focus.isValid()) {
|
if (activate == null || !activate.isValid()) {
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
recorder.requestFocus(focus);
|
recorder.requestActivation(activate);
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1225,24 +1222,24 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSynchronizeFocus(boolean enabled) {
|
public void setSynchronizeActive(boolean enabled) {
|
||||||
synchronizeFocus.set(enabled, null);
|
synchronizeActive.set(enabled, null);
|
||||||
// TODO: Which action to take here, if any?
|
// TODO: Which action to take here, if any?
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSynchronizeFocus() {
|
public boolean isSynchronizeActive() {
|
||||||
return synchronizeFocus.get();
|
return synchronizeActive.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener) {
|
public void addSynchronizeActiveChangeListener(BooleanChangeAdapter listener) {
|
||||||
synchronizeFocus.addChangeListener(listener);
|
synchronizeActive.addChangeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener) {
|
public void removeSynchronizeActiveChangeListener(BooleanChangeAdapter listener) {
|
||||||
synchronizeFocus.removeChangeListener(listener);
|
synchronizeActive.removeChangeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -54,7 +54,7 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
START_RECORDING,
|
START_RECORDING,
|
||||||
/**
|
/**
|
||||||
* The change was driven by the model focus, possibly indirectly by the user
|
* The change was driven by the model activation, possibly indirectly by the user
|
||||||
*/
|
*/
|
||||||
SYNC_MODEL,
|
SYNC_MODEL,
|
||||||
/**
|
/**
|
||||||
|
@ -281,11 +281,11 @@ public interface DebuggerTraceManagerService {
|
||||||
*
|
*
|
||||||
* @param coordinates the desired coordinates
|
* @param coordinates the desired coordinates
|
||||||
* @param cause the cause of the activation
|
* @param cause the cause of the activation
|
||||||
* @param syncTargetFocus true synchronize the current target to the same coordinates
|
* @param syncTarget true synchronize the current target to the same coordinates
|
||||||
* @return a future which completes when emulation and navigation is complete
|
* @return a future which completes when emulation and navigation is complete
|
||||||
*/
|
*/
|
||||||
CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
|
CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
|
||||||
ActivationCause cause, boolean syncTargetFocus);
|
ActivationCause cause, boolean syncTarget);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate the given coordinates, caused by the user
|
* Activate the given coordinates, caused by the user
|
||||||
|
@ -314,7 +314,7 @@ public interface DebuggerTraceManagerService {
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The manager may use a variety of sources of context including the current trace, the last
|
* The manager may use a variety of sources of context including the current trace, the last
|
||||||
* coordinates for a trace, the target's last/current focus, the list of active threads, etc.
|
* coordinates for a trace, the target's last/current activation, the list of live threads, etc.
|
||||||
*
|
*
|
||||||
* @param trace the trace
|
* @param trace the trace
|
||||||
* @return the best coordinates
|
* @return the best coordinates
|
||||||
|
@ -448,32 +448,32 @@ public interface DebuggerTraceManagerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control whether trace activation is synchronized with debugger focus/select
|
* Control whether trace activation is synchronized with debugger activation
|
||||||
*
|
*
|
||||||
* @param enabled true to synchronize, false otherwise
|
* @param enabled true to synchronize, false otherwise
|
||||||
*/
|
*/
|
||||||
void setSynchronizeFocus(boolean enabled);
|
void setSynchronizeActive(boolean enabled);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether trace activation is synchronized with debugger focus/select
|
* Check whether trace activation is synchronized with debugger activation
|
||||||
*
|
*
|
||||||
* @return true if synchronized, false otherwise
|
* @return true if synchronized, false otherwise
|
||||||
*/
|
*/
|
||||||
boolean isSynchronizeFocus();
|
boolean isSynchronizeActive();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a listener for changes to focus synchronization enablement
|
* Add a listener for changes to activation synchronization enablement
|
||||||
*
|
*
|
||||||
* @param listener the listener to receive change notifications
|
* @param listener the listener to receive change notifications
|
||||||
*/
|
*/
|
||||||
void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
void addSynchronizeActiveChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a listener for changes to focus synchronization enablement
|
* Remove a listener for changes to activation synchronization enablement
|
||||||
*
|
*
|
||||||
* @param listener the listener receiving change notifications
|
* @param listener the listener receiving change notifications
|
||||||
*/
|
*/
|
||||||
void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
void removeSynchronizeActiveChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control whether traces should be saved by default
|
* Control whether traces should be saved by default
|
||||||
|
|
|
@ -722,6 +722,13 @@ public interface TraceRecorder {
|
||||||
*/
|
*/
|
||||||
boolean isSupportsFocus();
|
boolean isSupportsFocus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the target is subject to a {@link TargetActiveScope}.
|
||||||
|
*
|
||||||
|
* @return true if an applicable scope is found, false otherwise.
|
||||||
|
*/
|
||||||
|
boolean isSupportsActivation();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the last-focused object as observed by this recorder
|
* Get the last-focused object as observed by this recorder
|
||||||
*
|
*
|
||||||
|
@ -748,6 +755,14 @@ public interface TraceRecorder {
|
||||||
*/
|
*/
|
||||||
CompletableFuture<Boolean> requestFocus(TargetObject focus);
|
CompletableFuture<Boolean> requestFocus(TargetObject focus);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request activation of a successor of the target
|
||||||
|
*
|
||||||
|
* @param active the object to activate
|
||||||
|
* @return a future which completes with true if the operation was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
CompletableFuture<Boolean> requestActivation(TargetObject active);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for pending transactions finish execution.
|
* Wait for pending transactions finish execution.
|
||||||
*
|
*
|
||||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.model;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -25,6 +26,8 @@ import org.junit.*;
|
||||||
|
|
||||||
import docking.widgets.table.DynamicTableColumn;
|
import docking.widgets.table.DynamicTableColumn;
|
||||||
import docking.widgets.table.GDynamicColumnTableModel;
|
import docking.widgets.table.GDynamicColumnTableModel;
|
||||||
|
import docking.widgets.tree.GTree;
|
||||||
|
import docking.widgets.tree.GTreeNode;
|
||||||
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
|
@ -52,45 +55,44 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
CTX = XmlSchemaContext.deserialize(
|
CTX = XmlSchemaContext.deserialize("""
|
||||||
"""
|
<context>
|
||||||
<context>
|
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
|
||||||
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
|
<attribute name='Processes' schema='ProcessContainer' />
|
||||||
<attribute name='Processes' schema='ProcessContainer' />
|
<interface name='EventScope' />
|
||||||
<interface name='EventScope' />
|
</schema>
|
||||||
</schema>
|
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
|
||||||
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
|
attributeResync='ONCE'>
|
||||||
attributeResync='ONCE'>
|
<element schema='Process' />
|
||||||
<element schema='Process' />
|
</schema>
|
||||||
</schema>
|
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
|
||||||
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
|
<attribute name='Threads' schema='ThreadContainer' />
|
||||||
<attribute name='Threads' schema='ThreadContainer' />
|
<attribute name='Handles' schema='HandleContainer' />
|
||||||
<attribute name='Handles' schema='HandleContainer' />
|
</schema>
|
||||||
</schema>
|
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
||||||
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
attributeResync='ONCE'>
|
||||||
attributeResync='ONCE'>
|
<element schema='Thread' />
|
||||||
<element schema='Thread' />
|
</schema>
|
||||||
</schema>
|
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
|
||||||
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
|
<interface name='Thread' />
|
||||||
<interface name='Thread' />
|
<attribute name='_display' schema='STRING' />
|
||||||
<attribute name='_display' schema='STRING' />
|
<attribute name='_self' schema='Thread' />
|
||||||
<attribute name='_self' schema='Thread' />
|
<attribute name='Stack' schema='Stack' />
|
||||||
<attribute name='Stack' schema='Stack' />
|
</schema>
|
||||||
</schema>
|
<schema name='Stack' canonical='yes' elementResync='NEVER'
|
||||||
<schema name='Stack' canonical='yes' elementResync='NEVER'
|
attributeResync='ONCE'>
|
||||||
attributeResync='ONCE'>
|
<interface name='Stack' />
|
||||||
<interface name='Stack' />
|
<element schema='Frame' />
|
||||||
<element schema='Frame' />
|
</schema>
|
||||||
</schema>
|
<schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>
|
||||||
<schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>
|
<interface name='StackFrame' />
|
||||||
<interface name='StackFrame' />
|
</schema>
|
||||||
</schema>
|
<schema name='HandleContainer' canonical='yes' elementResync='NEVER'
|
||||||
<schema name='HandleContainer' canonical='yes' elementResync='NEVER'
|
attributeResync='ONCE'>
|
||||||
attributeResync='ONCE'>
|
<element schema='INT' />
|
||||||
<element schema='INT' />
|
</schema>
|
||||||
</schema>
|
</context>
|
||||||
</context>
|
""");
|
||||||
""");
|
|
||||||
}
|
}
|
||||||
catch (JDOMException e) {
|
catch (JDOMException e) {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
|
@ -241,9 +243,6 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
protected void assertPathIs(TraceObjectKeyPath path, int elemCount, int attrCount) {
|
protected void assertPathIs(TraceObjectKeyPath path, int elemCount, int attrCount) {
|
||||||
assertEquals(path, modelProvider.getPath());
|
assertEquals(path, modelProvider.getPath());
|
||||||
assertEquals(path.toString(), modelProvider.pathField.getText());
|
assertEquals(path.toString(), modelProvider.pathField.getText());
|
||||||
AbstractNode item = modelProvider.objectsTreePanel.getSelectedItem();
|
|
||||||
assertNotNull(item);
|
|
||||||
assertEquals(path, item.getValue().getChild().getCanonicalPath());
|
|
||||||
// Table model is threaded
|
// Table model is threaded
|
||||||
waitForPass(() -> assertEquals(elemCount,
|
waitForPass(() -> assertEquals(elemCount,
|
||||||
modelProvider.elementsTablePanel.tableModel.getModelData().size()));
|
modelProvider.elementsTablePanel.tableModel.getModelData().size()));
|
||||||
|
@ -394,13 +393,40 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
assertPathIsThreadsContainer();
|
assertPathIsThreadsContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoubleClickObjectInObjectsTree() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
|
||||||
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
TraceObject root = objects.getRootObject();
|
||||||
|
TraceObjectKeyPath processesPath = TraceObjectKeyPath.parse("Processes");
|
||||||
|
TraceObject processes = objects.getObjectByCanonicalPath(processesPath);
|
||||||
|
traceManager.activateObject(root);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
modelProvider.setTreeSelection(processesPath, EventOrigin.USER_GENERATED);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
GTree tree = modelProvider.objectsTreePanel.tree;
|
||||||
|
GTreeNode node = waitForPass(() -> {
|
||||||
|
GTreeNode n = Unique.assertOne(tree.getSelectedNodes());
|
||||||
|
assertEquals("Processes", n.getName());
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
clickTreeNode(tree, node, MouseEvent.BUTTON1);
|
||||||
|
clickTreeNode(tree, node, MouseEvent.BUTTON1);
|
||||||
|
waitForSwing();
|
||||||
|
waitForPass(() -> assertEquals(processes, traceManager.getCurrentObject()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDoubleClickLinkInElementsTable() throws Throwable {
|
public void testDoubleClickLinkInElementsTable() throws Throwable {
|
||||||
createTraceAndPopulateObjects();
|
createTraceAndPopulateObjects();
|
||||||
|
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Links"));
|
TraceObjectKeyPath pathLinks = TraceObjectKeyPath.parse("Processes[0].Links");
|
||||||
|
modelProvider.setPath(pathLinks);
|
||||||
waitForTasks();
|
waitForTasks();
|
||||||
|
|
||||||
ValueRow row2 = waitForValue(() -> {
|
ValueRow row2 = waitForValue(() -> {
|
||||||
|
@ -421,7 +447,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
});
|
});
|
||||||
clickTableCell(modelProvider.elementsTablePanel.table, rowIndex, 0, 2);
|
clickTableCell(modelProvider.elementsTablePanel.table, rowIndex, 0, 2);
|
||||||
|
|
||||||
assertPathIs(TraceObjectKeyPath.parse("Processes[0].Threads[7]"), 0, 3);
|
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads[7]"),
|
||||||
|
traceManager.getCurrentObject().getCanonicalPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -451,7 +478,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
});
|
});
|
||||||
clickTableCell(modelProvider.elementsTablePanel.table, rowIndex, 0, 2);
|
clickTableCell(modelProvider.elementsTablePanel.table, rowIndex, 0, 2);
|
||||||
|
|
||||||
assertPathIs(TraceObjectKeyPath.parse("Processes[0].Threads[2]"), 0, 3);
|
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads[2]"),
|
||||||
|
traceManager.getCurrentObject().getCanonicalPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void selectAttribute(String key) {
|
protected void selectAttribute(String key) {
|
||||||
|
@ -492,7 +520,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
});
|
});
|
||||||
clickTableCell(modelProvider.attributesTablePanel.table, rowIndex, 0, 2);
|
clickTableCell(modelProvider.attributesTablePanel.table, rowIndex, 0, 2);
|
||||||
|
|
||||||
assertPathIs(TraceObjectKeyPath.parse("Processes[0].Threads[3]"), 0, 5);
|
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads[3]"),
|
||||||
|
traceManager.getCurrentObject().getCanonicalPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -528,7 +557,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
});
|
});
|
||||||
clickTableCell(modelProvider.attributesTablePanel.table, rowIndex, 0, 2);
|
clickTableCell(modelProvider.attributesTablePanel.table, rowIndex, 0, 2);
|
||||||
|
|
||||||
assertPathIsThreadsContainer();
|
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads"),
|
||||||
|
traceManager.getCurrentObject().getCanonicalPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -565,7 +595,9 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
|
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[2]"));
|
TraceObjectKeyPath thread2Path = TraceObjectKeyPath.parse("Processes[0].Threads[2]");
|
||||||
|
modelProvider.setPath(thread2Path);
|
||||||
|
modelProvider.setTreeSelection(thread2Path);
|
||||||
waitForTasks();
|
waitForTasks();
|
||||||
|
|
||||||
AbstractNode nodeThread2 =
|
AbstractNode nodeThread2 =
|
||||||
|
@ -603,7 +635,6 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
|
|
||||||
TraceObjectKeyPath thread3Path = TraceObjectKeyPath.parse("Processes[0].Threads[3]");
|
TraceObjectKeyPath thread3Path = TraceObjectKeyPath.parse("Processes[0].Threads[3]");
|
||||||
assertPathIs(thread3Path, 0, 5);
|
assertPathIs(thread3Path, 0, 5);
|
||||||
assertEquals(thread3Path, traceManager.getCurrentObject().getCanonicalPath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -790,52 +821,6 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
waitForPass(() -> assertEquals("<html>Renamed Thread", node.getDisplayText()));
|
waitForPass(() -> assertEquals("<html>Renamed Thread", node.getDisplayText()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTreeSelectionActivatesObject() throws Throwable {
|
|
||||||
createTraceAndPopulateObjects();
|
|
||||||
|
|
||||||
TraceObjectManager objects = tb.trace.getObjectManager();
|
|
||||||
TraceObject root = objects.getRootObject();
|
|
||||||
TraceObjectKeyPath processesPath = TraceObjectKeyPath.parse("Processes");
|
|
||||||
TraceObject processes = objects.getObjectByCanonicalPath(processesPath);
|
|
||||||
traceManager.activateObject(root);
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
modelProvider.setTreeSelection(processesPath, EventOrigin.USER_GENERATED);
|
|
||||||
waitForSwing();
|
|
||||||
assertEquals(processes, traceManager.getCurrentObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testElementSelectionActivatesObject() throws Throwable {
|
|
||||||
createTraceAndPopulateObjects();
|
|
||||||
TraceObjectManager objects = tb.trace.getObjectManager();
|
|
||||||
TraceObject processes =
|
|
||||||
objects.getObjectByCanonicalPath(TraceObjectKeyPath.parse("Processes"));
|
|
||||||
TraceObject process0 = processes.getElement(0, 0).getChild();
|
|
||||||
traceManager.activateObject(processes);
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
assertTrue(modelProvider.elementsTablePanel.trySelect(process0));
|
|
||||||
waitForSwing();
|
|
||||||
assertEquals(process0, traceManager.getCurrentObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAttributeSelectionActivatesObject() throws Throwable {
|
|
||||||
createTraceAndPopulateObjects();
|
|
||||||
|
|
||||||
TraceObjectManager objects = tb.trace.getObjectManager();
|
|
||||||
TraceObject root = objects.getRootObject();
|
|
||||||
TraceObject processes = root.getAttribute(0, "Processes").getChild();
|
|
||||||
traceManager.activateObject(root);
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
assertTrue(modelProvider.attributesTablePanel.trySelect(processes));
|
|
||||||
waitForSwing();
|
|
||||||
assertEquals(processes, traceManager.getCurrentObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testObjectActivationSelectsTree() throws Throwable {
|
public void testObjectActivationSelectsTree() throws Throwable {
|
||||||
createTraceAndPopulateObjects();
|
createTraceAndPopulateObjects();
|
||||||
|
@ -865,11 +850,12 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
|
|
||||||
traceManager.activateObject(processes);
|
traceManager.activateObject(processes);
|
||||||
waitForTasks();
|
waitForTasks();
|
||||||
assertEquals(processes, modelProvider.getTreeSelection().getChild());
|
modelProvider.setTreeSelection(processes.getCanonicalPath());
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
// TODO: Is this the desired behavior?
|
|
||||||
traceManager.activateObject(root);
|
traceManager.activateObject(root);
|
||||||
waitForTasks();
|
waitForTasks();
|
||||||
|
// TODO: Is this the desired behavior?
|
||||||
assertEquals(processes, modelProvider.getTreeSelection().getChild());
|
assertEquals(processes, modelProvider.getTreeSelection().getChild());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -925,6 +911,12 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
TraceObject processes = root.getAttribute(0, "Processes").getChild();
|
TraceObject processes = root.getAttribute(0, "Processes").getChild();
|
||||||
traceManager.activateObject(root);
|
traceManager.activateObject(root);
|
||||||
waitForTasks();
|
waitForTasks();
|
||||||
|
// Warm it up a bit. TODO: This is kind of cheating.
|
||||||
|
traceManager.activateObject(processes);
|
||||||
|
waitForTasks();
|
||||||
|
traceManager.activateObject(root);
|
||||||
|
modelProvider.setPath(root.getCanonicalPath());
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: It's interesting that activating a parent then a child produces a different end
|
* TODO: It's interesting that activating a parent then a child produces a different end
|
||||||
|
|
|
@ -17,11 +17,11 @@ package ghidra.app.plugin.core.debug.gui.stack;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
|
@ -404,32 +404,6 @@ public class DebuggerStackProviderLegacyTest extends AbstractGhidraHeadedDebugge
|
||||||
assertProviderEmpty();
|
assertProviderEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore("TODO") // Not sure why this fails under Gradle but not my IDE
|
|
||||||
public void testSelectRowActivatesFrame() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
|
|
||||||
TraceThread thread = addThread("Processes[1].Threads[1]");
|
|
||||||
TraceStack stack = addStack(thread);
|
|
||||||
addStackFrames(stack);
|
|
||||||
waitForDomainObject(tb.trace);
|
|
||||||
|
|
||||||
traceManager.activateThread(thread);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertProviderPopulated();
|
|
||||||
|
|
||||||
clickTableCellWithButton(stackProvider.legacyPanel.stackTable, 0, 0, MouseEvent.BUTTON1);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertEquals(0, traceManager.getCurrentFrame());
|
|
||||||
|
|
||||||
clickTableCellWithButton(stackProvider.legacyPanel.stackTable, 1, 0, MouseEvent.BUTTON1);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertEquals(1, traceManager.getCurrentFrame());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateFrameSelectsRow() throws Exception {
|
public void testActivateFrameSelectsRow() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
|
@ -455,6 +429,27 @@ public class DebuggerStackProviderLegacyTest extends AbstractGhidraHeadedDebugge
|
||||||
assertEquals(1, stackProvider.legacyPanel.stackTable.getSelectedRow());
|
assertEquals(1, stackProvider.legacyPanel.stackTable.getSelectedRow());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoubleClickRowActivatesFrame() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
|
||||||
|
TraceThread thread = addThread("Processes[1].Threads[1]");
|
||||||
|
TraceStack stack = addStack(thread);
|
||||||
|
addStackFrames(stack);
|
||||||
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
|
traceManager.activateThread(thread);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertProviderPopulated();
|
||||||
|
|
||||||
|
clickTableCell(stackProvider.legacyPanel.stackTable, 0, 0, 2);
|
||||||
|
assertEquals(0, traceManager.getCurrentFrame());
|
||||||
|
|
||||||
|
clickTableCell(stackProvider.legacyPanel.stackTable, 1, 0, 2);
|
||||||
|
assertEquals(1, traceManager.getCurrentFrame());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateThenAddMappingPopulatesFunctionColumn() throws Exception {
|
public void testActivateThenAddMappingPopulatesFunctionColumn() throws Exception {
|
||||||
createTrace();
|
createTrace();
|
||||||
|
|
|
@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
|
||||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
|
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
|
||||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
||||||
import ghidra.app.services.DebuggerStaticMappingService;
|
import ghidra.app.services.DebuggerStaticMappingService;
|
||||||
|
@ -487,33 +488,6 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
waitForPass(() -> assertProviderEmpty());
|
waitForPass(() -> assertProviderEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSelectRowActivateFrame() throws Exception {
|
|
||||||
createAndOpenTrace();
|
|
||||||
|
|
||||||
TraceObjectThread thread = addThread(1);
|
|
||||||
TraceObjectStack stack = addStack(thread);
|
|
||||||
addStackFrames(stack);
|
|
||||||
waitForDomainObject(tb.trace);
|
|
||||||
|
|
||||||
traceManager.activateObject(thread.getObject());
|
|
||||||
waitForTasks();
|
|
||||||
|
|
||||||
waitForPass(() -> assertProviderPopulated());
|
|
||||||
|
|
||||||
TraceObject frame0 = stack.getObject().getElement(0, 0).getChild();
|
|
||||||
TraceObject frame1 = stack.getObject().getElement(0, 1).getChild();
|
|
||||||
List<ValueRow> allItems = stackProvider.panel.getAllItems();
|
|
||||||
|
|
||||||
stackProvider.panel.setSelectedItem(allItems.get(1));
|
|
||||||
waitForTasks();
|
|
||||||
waitForPass(() -> assertEquals(frame1, traceManager.getCurrentObject()));
|
|
||||||
|
|
||||||
stackProvider.panel.setSelectedItem(allItems.get(0));
|
|
||||||
waitForTasks();
|
|
||||||
waitForPass(() -> assertEquals(frame0, traceManager.getCurrentObject()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateFrameSelectsRow() throws Exception {
|
public void testActivateFrameSelectsRow() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
|
@ -541,6 +515,32 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
waitForPass(() -> assertEquals(allItems.get(0), stackProvider.panel.getSelectedItem()));
|
waitForPass(() -> assertEquals(allItems.get(0), stackProvider.panel.getSelectedItem()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoubleClickRowActivateFrame() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
|
||||||
|
TraceObjectThread thread = addThread(1);
|
||||||
|
TraceObjectStack stack = addStack(thread);
|
||||||
|
addStackFrames(stack);
|
||||||
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
|
traceManager.activateObject(thread.getObject());
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
waitForPass(() -> assertProviderPopulated());
|
||||||
|
|
||||||
|
TraceObject frame0 = stack.getObject().getElement(0, 0).getChild();
|
||||||
|
TraceObject frame1 = stack.getObject().getElement(0, 1).getChild();
|
||||||
|
|
||||||
|
clickTableCell(QueryPanelTestHelper.getTable(stackProvider.panel), 1, 0, 2);
|
||||||
|
waitForTasks();
|
||||||
|
waitForPass(() -> assertEquals(frame1, traceManager.getCurrentObject()));
|
||||||
|
|
||||||
|
clickTableCell(QueryPanelTestHelper.getTable(stackProvider.panel), 0, 0, 2);
|
||||||
|
waitForTasks();
|
||||||
|
waitForPass(() -> assertEquals(frame0, traceManager.getCurrentObject()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateTheAddMappingPopulatesFunctionColumn() throws Exception {
|
public void testActivateTheAddMappingPopulatesFunctionColumn() throws Exception {
|
||||||
createTrace();
|
createTrace();
|
||||||
|
|
|
@ -364,21 +364,16 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSelectThreadInTableActivatesThread() throws Exception {
|
public void testDoubleClickThreadInTableActivatesThread() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
assertThreadsPopulated();
|
assertThreadsPopulated();
|
||||||
assertThreadSelected(thread1); // Manager selects default if not live
|
|
||||||
|
|
||||||
clickTableCellWithButton(threadsProvider.legacyPanel.threadTable, 1, 0, MouseEvent.BUTTON1);
|
clickTableCell(threadsProvider.legacyPanel.threadTable, 1, 0, 2);
|
||||||
|
assertEquals(thread2, traceManager.getCurrentThread());
|
||||||
waitForPass(() -> {
|
|
||||||
assertThreadSelected(thread2);
|
|
||||||
assertEquals(thread2, traceManager.getCurrentThread());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -397,5 +392,4 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||||
|
|
||||||
assertEquals(6, threadsProvider.legacyPanel.headerRenderer.getCursorPosition().longValue());
|
assertEquals(6, threadsProvider.legacyPanel.headerRenderer.getCursorPosition().longValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,26 +112,26 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
public void activateObjectsMode() throws Exception {
|
public void activateObjectsMode() throws Exception {
|
||||||
// NOTE the use of index='1' allowing object-based managers to ID unique path
|
// NOTE the use of index='1' allowing object-based managers to ID unique path
|
||||||
ctx = XmlSchemaContext.deserialize("" + //
|
ctx = XmlSchemaContext.deserialize("""
|
||||||
"<context>" + //
|
<context>
|
||||||
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
|
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
|
||||||
" <attribute name='Processes' schema='ProcessContainer' />" + //
|
<attribute name='Processes' schema='ProcessContainer' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='ProcessContainer' canonical='yes' elementResync='NEVER' " + //
|
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
|
||||||
" attributeResync='ONCE'>" + //
|
attributeResync='ONCE'>
|
||||||
" <element index='1' schema='Process' />" + // <---- NOTE HERE
|
<element index='1' schema='Process' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>" + //
|
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
|
||||||
" <attribute name='Threads' schema='ThreadContainer' />" + //
|
<attribute name='Threads' schema='ThreadContainer' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + //
|
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
||||||
" attributeResync='ONCE'>" + //
|
attributeResync='ONCE'>
|
||||||
" <element schema='Thread' />" + //
|
<element schema='Thread' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + //
|
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
|
||||||
" <interface name='Thread' />" + //
|
<interface name='Thread' />
|
||||||
" </schema>" + //
|
</schema>
|
||||||
"</context>");
|
</context>""");
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
|
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||||
|
@ -521,25 +521,18 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSelectThreadInTableActivatesThread() throws Exception {
|
public void testDoubleClickThreadInTableActivatesThread() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
waitForTasks();
|
waitForTasks();
|
||||||
|
|
||||||
waitForPass(() -> {
|
waitForPass(() -> assertThreadsPopulated());
|
||||||
assertThreadsPopulated();
|
|
||||||
assertThreadSelected(thread1); // Manager selects default if not live
|
|
||||||
});
|
|
||||||
|
|
||||||
GhidraTable table = QueryPanelTestHelper.getTable(provider.panel);
|
GhidraTable table = QueryPanelTestHelper.getTable(provider.panel);
|
||||||
clickTableCellWithButton(table, 1, 0, MouseEvent.BUTTON1);
|
clickTableCell(table, 1, 0, 2);
|
||||||
|
assertEquals(thread2, traceManager.getCurrentThread());
|
||||||
waitForPass(() -> {
|
|
||||||
assertThreadSelected(thread2);
|
|
||||||
assertEquals(thread2, traceManager.getCurrentThread());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -286,31 +286,6 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
tb.trace.getTimeManager().getSnapshot(0, false).getDescription());
|
tb.trace.getTimeManager().getSnapshot(0, false).getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSelectRowActivatesSnap() throws Exception {
|
|
||||||
createSnaplessTrace();
|
|
||||||
traceManager.openTrace(tb.trace);
|
|
||||||
addSnapshots();
|
|
||||||
waitForDomainObject(tb.trace);
|
|
||||||
|
|
||||||
assertProviderEmpty();
|
|
||||||
|
|
||||||
traceManager.activateTrace(tb.trace);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
|
||||||
|
|
||||||
timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(0));
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertEquals(0, traceManager.getCurrentSnap());
|
|
||||||
|
|
||||||
timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(1));
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertEquals(10, traceManager.getCurrentSnap());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateSnapSelectsRow() throws Exception {
|
public void testActivateSnapSelectsRow() throws Exception {
|
||||||
createSnaplessTrace();
|
createSnaplessTrace();
|
||||||
|
@ -341,6 +316,25 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
assertNull(timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
|
assertNull(timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoubleClickRowActivatesSnap() throws Exception {
|
||||||
|
createSnaplessTrace();
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
addSnapshots();
|
||||||
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
|
assertProviderEmpty();
|
||||||
|
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
clickTableCell(timeProvider.mainPanel.snapshotTable, 0, 0, 2);
|
||||||
|
assertEquals(0, traceManager.getCurrentSnap());
|
||||||
|
|
||||||
|
clickTableCell(timeProvider.mainPanel.snapshotTable, 1, 0, 2);
|
||||||
|
assertEquals(10, traceManager.getCurrentSnap());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddScratchThenActivateIsHidden() throws Exception {
|
public void testAddScratchThenActivateIsHidden() throws Exception {
|
||||||
createSnaplessTrace();
|
createSnaplessTrace();
|
||||||
|
|
|
@ -392,7 +392,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSynchronizeFocusTraceToModel() throws Throwable {
|
public void testSynchronizeFocusTraceToModel() throws Throwable {
|
||||||
assertTrue(traceManager.isSynchronizeFocus());
|
assertTrue(traceManager.isSynchronizeActive());
|
||||||
|
|
||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
|
@ -444,7 +444,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
waitForPass(() -> assertEquals(frame0, mb.testModel.session.getFocus()));
|
waitForPass(() -> assertEquals(frame0, mb.testModel.session.getFocus()));
|
||||||
|
|
||||||
traceManager.setSynchronizeFocus(false);
|
traceManager.setSynchronizeActive(false);
|
||||||
traceManager.activateFrame(1);
|
traceManager.activateFrame(1);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
|
@ -453,7 +453,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSynchronizeFocusModelToTrace() throws Throwable {
|
public void testSynchronizeFocusModelToTrace() throws Throwable {
|
||||||
assertTrue(traceManager.isSynchronizeFocus());
|
assertTrue(traceManager.isSynchronizeActive());
|
||||||
|
|
||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
|
@ -499,7 +499,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
waitForPass(() -> assertEquals(0, traceManager.getCurrentFrame()));
|
waitForPass(() -> assertEquals(0, traceManager.getCurrentFrame()));
|
||||||
|
|
||||||
traceManager.setSynchronizeFocus(false);
|
traceManager.setSynchronizeActive(false);
|
||||||
waitOn(mb.testModel.session.requestFocus(frame1));
|
waitOn(mb.testModel.session.requestFocus(frame1));
|
||||||
// Not super reliable, but at least wait for it to change in case it does
|
// Not super reliable, but at least wait for it to change in case it does
|
||||||
Thread.sleep(200);
|
Thread.sleep(200);
|
||||||
|
|
|
@ -152,7 +152,7 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
// allows us to change the font to bold as needed without erasing the original font
|
// allows us to change the font to bold as needed without erasing the original font
|
||||||
private Font getFont(boolean bold) {
|
protected Font getFont(boolean bold) {
|
||||||
Font font = getFont();
|
Font font = getFont();
|
||||||
// check if someone set a new font on the renderer
|
// check if someone set a new font on the renderer
|
||||||
if (font != cachedDefaultFont && font != cachedBoldFont) {
|
if (font != cachedDefaultFont && font != cachedBoldFont) {
|
||||||
|
|