mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 04:05:39 +00:00
Merge remote-tracking branch 'origin/GP-1608_refactorTraceTabs--SQUASHED'
This commit is contained in:
commit
f963f23a8f
@ -135,6 +135,13 @@ public interface Target {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the target for display in the UI
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
String describe();
|
||||
|
||||
/**
|
||||
* Check if the target is still valid
|
||||
*
|
||||
|
@ -45,6 +45,16 @@ import ghidra.util.NotOwnerException;
|
||||
|
||||
public class DebuggerCoordinates {
|
||||
|
||||
/**
|
||||
* Coordinates that indicate no trace is active in the Debugger UI.
|
||||
*
|
||||
* <p>
|
||||
* Typically, that only happens when no trace is open. Telling the trace manager to activate
|
||||
* {@code NOWHERE} will cause it to instead activate the most recently active trace, which may
|
||||
* very well be the current trace, resulting in no change. Internally, the trace manager will
|
||||
* activate {@code NOWHERE} whenever the current trace is closed, effectively activating the
|
||||
* most recent trace other than the one just closed.
|
||||
*/
|
||||
public static final DebuggerCoordinates NOWHERE =
|
||||
new DebuggerCoordinates(null, null, null, null, null, null, null, null);
|
||||
|
||||
|
@ -82,6 +82,12 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||
this.supportedBreakpointKinds = computeSupportedBreakpointKinds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describe() {
|
||||
return "%s in %s at %s (rmi)".formatted(getTrace().getDomainFile().getName(),
|
||||
connection.getDescription(), connection.getRemoteAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return !connection.isClosed() && connection.isTarget(trace);
|
||||
|
@ -1614,7 +1614,7 @@ public interface DebuggerResources {
|
||||
}
|
||||
|
||||
interface CloseAllTracesAction extends CloseTraceAction {
|
||||
String NAME = NAME_PREFIX + " All Traces";
|
||||
String NAME = NAME_PREFIX + "All Traces";
|
||||
String DESCRIPTION = "Close all traces";
|
||||
String HELP_ANCHOR = "close_all_traces";
|
||||
|
||||
@ -1641,7 +1641,7 @@ public interface DebuggerResources {
|
||||
}
|
||||
|
||||
interface CloseOtherTracesAction extends CloseTraceAction {
|
||||
String NAME = NAME_PREFIX + " Other Traces";
|
||||
String NAME = NAME_PREFIX + "Other Traces";
|
||||
String DESCRIPTION = "Close all traces except the current one";
|
||||
String HELP_ANCHOR = "close_other_traces";
|
||||
|
||||
@ -1668,7 +1668,7 @@ public interface DebuggerResources {
|
||||
}
|
||||
|
||||
interface CloseDeadTracesAction extends CloseTraceAction {
|
||||
String NAME = NAME_PREFIX + " Dead Traces";
|
||||
String NAME = NAME_PREFIX + "Dead Traces";
|
||||
String DESCRIPTION = "Close all traces not being recorded";
|
||||
String HELP_ANCHOR = "close_dead_traces";
|
||||
|
||||
|
@ -55,9 +55,6 @@ import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.trace.util.TraceEvents;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger global controls",
|
||||
@ -357,18 +354,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
TargetActionTask.executeTask(tool, new Task("Disconnect", false, false, false) {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
try {
|
||||
target.disconnect();
|
||||
}
|
||||
catch (Exception e) {
|
||||
tool.setStatusInfo("Disconnect failed: " + e, true);
|
||||
Msg.error(this, "Disconnect failed: " + e, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
TargetActionTask.executeTask(tool, new DisconnectTask(tool, List.of(target)));
|
||||
}
|
||||
|
||||
private boolean haveEmuAndTrace() {
|
||||
|
@ -0,0 +1,53 @@
|
||||
/* ###
|
||||
* 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.control;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DisconnectTask extends Task {
|
||||
private final PluginTool tool;
|
||||
private final List<Target> targets;
|
||||
|
||||
public DisconnectTask(PluginTool tool, Collection<Target> targets) {
|
||||
super("Disconnect", false, true, false);
|
||||
this.tool = tool;
|
||||
this.targets = List.copyOf(targets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
monitor.initialize(targets.size(), "Disconnecting...");
|
||||
for (Target target : targets) {
|
||||
try {
|
||||
monitor.setMessage("Disconnecting " + target.describe());
|
||||
target.disconnect();
|
||||
monitor.increment();
|
||||
}
|
||||
catch (Exception e) {
|
||||
tool.setStatusInfo("Disconnect failed: " + e, true);
|
||||
Msg.error(this, "Disconnect failed: " + e, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -57,6 +57,8 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAc
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.OpenProgramAction;
|
||||
import ghidra.app.plugin.core.debug.gui.action.*;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
|
||||
import ghidra.app.plugin.core.debug.gui.thread.DebuggerTraceFileActionContext;
|
||||
import ghidra.app.plugin.core.debug.gui.trace.DebuggerTraceTabPanel;
|
||||
import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
|
||||
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
||||
import ghidra.app.plugin.core.marker.MarkerMarginProvider;
|
||||
@ -342,6 +344,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||
protected final ListenerSet<LocationTrackingSpecChangeListener> trackingSpecChangeListeners =
|
||||
new ListenerSet<>(LocationTrackingSpecChangeListener.class, true);
|
||||
|
||||
protected final DebuggerTraceTabPanel traceTabs;
|
||||
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
|
||||
protected final JLabel trackingLabel = new JLabel();
|
||||
|
||||
@ -372,6 +375,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||
this.plugin = plugin;
|
||||
this.isMainListing = isConnected;
|
||||
|
||||
// TODO: An icon to distinguish dynamic from static
|
||||
|
||||
syncTrait = new ForListingSyncTrait();
|
||||
goToTrait = new ForListingGoToTrait();
|
||||
trackingTrait = new ForListingTrackingTrait();
|
||||
@ -394,13 +399,21 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||
readsMemTrait.goToCoordinates(current);
|
||||
locationLabel.goToCoordinates(current);
|
||||
|
||||
// TODO: An icon to distinguish dynamic from static
|
||||
if (isConnected) {
|
||||
traceTabs = new DebuggerTraceTabPanel(plugin);
|
||||
}
|
||||
else {
|
||||
traceTabs = null;
|
||||
}
|
||||
|
||||
addDisplayListener(readsMemTrait.getDisplayListener());
|
||||
|
||||
JPanel northPanel = new JPanel(new BorderLayout());
|
||||
northPanel.add(locationLabel);
|
||||
northPanel.add(trackingLabel, BorderLayout.EAST);
|
||||
if (traceTabs != null) {
|
||||
northPanel.add(traceTabs, BorderLayout.NORTH);
|
||||
}
|
||||
this.setNorthComponent(northPanel);
|
||||
if (isConnected) {
|
||||
setTitle(DebuggerResources.TITLE_PROVIDER_LISTING);
|
||||
@ -929,6 +942,12 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
if (traceTabs != null) {
|
||||
DebuggerTraceFileActionContext traceCtx = traceTabs.getActionContext(event);
|
||||
if (traceCtx != null) {
|
||||
return traceCtx;
|
||||
}
|
||||
}
|
||||
if (event == null || event.getSource() != locationLabel) {
|
||||
return super.getActionContext(event);
|
||||
}
|
||||
@ -1036,7 +1055,6 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Attempt to open probable matches. All others, list to import
|
||||
// TODO: What if sections are not presented?
|
||||
for (TraceModule mod : modules) {
|
||||
DomainFile match = mappingService.findBestModuleProgram(space, mod);
|
||||
if (match == null) {
|
||||
@ -1064,8 +1082,9 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||
new DebuggerMissingModuleActionContext(mod));
|
||||
}
|
||||
/**
|
||||
* Once the programs are opened, including those which are successfully imported, the mapper
|
||||
* bot should take over, eventually invoking callbacks to our mapping change listener.
|
||||
* Once the programs are opened, including those which are successfully imported, the
|
||||
* automatic mapper should take effect, eventually invoking callbacks to our mapping change
|
||||
* listener.
|
||||
*/
|
||||
}
|
||||
|
||||
|
@ -76,6 +76,14 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||
coords);
|
||||
}
|
||||
|
||||
DebuggerCoordinates coordsForObject(TraceObject object) {
|
||||
if (provider.current.getTrace() != object.getTrace()) {
|
||||
// This can happen transiently, so just find something graceful
|
||||
return DebuggerCoordinates.NOWHERE.object(object);
|
||||
}
|
||||
return provider.current.object(object);
|
||||
}
|
||||
|
||||
private class ThreadPcColumn extends TraceValueObjectPropertyColumn<Address> {
|
||||
public ThreadPcColumn() {
|
||||
super(Address.class);
|
||||
@ -84,7 +92,8 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||
@Override
|
||||
public ValueProperty<Address> getProperty(ValueRow row) {
|
||||
TraceObject obj = row.getValue().getChild();
|
||||
DebuggerCoordinates coords = provider.current.object(obj);
|
||||
|
||||
DebuggerCoordinates coords = coordsForObject(obj);
|
||||
return new ValueAddressProperty(row) {
|
||||
@Override
|
||||
public Address getValue() {
|
||||
@ -110,7 +119,7 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||
public Function getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
TraceObject obj = rowObject.getValue().getChild();
|
||||
DebuggerCoordinates coords = provider.current.object(obj);
|
||||
DebuggerCoordinates coords = coordsForObject(obj);
|
||||
Address pc = computeProgramCounter(coords);
|
||||
if (pc == null) {
|
||||
return null;
|
||||
@ -129,7 +138,7 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||
public String getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
TraceObject obj = rowObject.getValue().getChild();
|
||||
DebuggerCoordinates coords = provider.current.object(obj);
|
||||
DebuggerCoordinates coords = coordsForObject(obj);
|
||||
Address pc = computeProgramCounter(coords);
|
||||
if (pc == null) {
|
||||
return null;
|
||||
@ -146,7 +155,7 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
||||
@Override
|
||||
public ValueProperty<Address> getProperty(ValueRow row) {
|
||||
TraceObject obj = row.getValue().getChild();
|
||||
DebuggerCoordinates coords = provider.current.object(obj);
|
||||
DebuggerCoordinates coords = coordsForObject(obj);
|
||||
return new ValueAddressProperty(row) {
|
||||
@Override
|
||||
public Address getValue() {
|
||||
|
@ -28,7 +28,8 @@ import docking.WindowPosition;
|
||||
import docking.action.DockingActionIf;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.DebuggerEmulationService;
|
||||
import ghidra.app.services.DebuggerTargetService;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.model.DomainObjectChangeRecord;
|
||||
import ghidra.framework.model.DomainObjectEvent;
|
||||
@ -97,8 +98,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
|
||||
@AutoServiceConsumed
|
||||
DebuggerTargetService targetService;
|
||||
// @AutoServiceConsumed // via method
|
||||
private DebuggerTraceManagerService traceManager;
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
@ -106,7 +105,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
|
||||
private JPanel mainPanel;
|
||||
|
||||
DebuggerTraceTabPanel traceTabs;
|
||||
JPopupMenu traceTabPopupMenu;
|
||||
DebuggerThreadsPanel panel;
|
||||
DebuggerLegacyThreadsPanel legacyPanel;
|
||||
@ -133,12 +131,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
public void setTraceManager(DebuggerTraceManagerService traceManager) {
|
||||
this.traceManager = traceManager;
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
public void setEmulationService(DebuggerEmulationService emulationService) {
|
||||
contextChanged();
|
||||
@ -152,7 +144,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
|
||||
current = coordinates;
|
||||
|
||||
traceTabs.coordinatesActivated(coordinates);
|
||||
if (Trace.isLegacy(coordinates.getTrace())) {
|
||||
panel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
||||
legacyPanel.coordinatesActivated(coordinates);
|
||||
@ -191,10 +182,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
myActionContext = legacyPanel.getActionContext();
|
||||
}
|
||||
|
||||
void traceTabsContextChanged() {
|
||||
myActionContext = traceTabs.getActionContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
if (myActionContext == null) {
|
||||
@ -211,10 +198,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
panel = new DebuggerThreadsPanel(this);
|
||||
legacyPanel = new DebuggerLegacyThreadsPanel(plugin, this);
|
||||
mainPanel.add(panel);
|
||||
|
||||
traceTabs = new DebuggerTraceTabPanel(this);
|
||||
|
||||
mainPanel.add(traceTabs, BorderLayout.NORTH);
|
||||
}
|
||||
|
||||
protected void createActions() {
|
||||
|
@ -13,27 +13,24 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.thread;
|
||||
package ghidra.app.plugin.core.debug.gui.trace;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.*;
|
||||
import java.util.Objects;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.widgets.HorizontalTabPanel;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
|
||||
import docking.widgets.tab.GTabPanel;
|
||||
import ghidra.app.plugin.core.debug.event.*;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
import ghidra.app.plugin.core.debug.gui.thread.DebuggerTraceFileActionContext;
|
||||
import ghidra.app.plugin.core.progmgr.MultiTabPlugin;
|
||||
import ghidra.app.services.DebuggerTargetService;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.TargetPublicationListener;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginEventListener;
|
||||
@ -42,26 +39,21 @@ import ghidra.util.Swing;
|
||||
import utilities.util.SuppressableCallback;
|
||||
import utilities.util.SuppressableCallback.Suppression;
|
||||
|
||||
public class DebuggerTraceTabPanel extends HorizontalTabPanel<Trace>
|
||||
implements PluginEventListener {
|
||||
public class DebuggerTraceTabPanel extends GTabPanel<Trace>
|
||||
implements PluginEventListener, DomainObjectListener {
|
||||
|
||||
private class TargetsChangeListener implements TargetPublicationListener {
|
||||
@Override
|
||||
public void targetPublished(Target target) {
|
||||
Swing.runIfSwingOrRunLater(() -> repaint());
|
||||
|
||||
Swing.runIfSwingOrRunLater(() -> refreshTab(target.getTrace()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void targetWithdrawn(Target target) {
|
||||
Swing.runIfSwingOrRunLater(() -> repaint());
|
||||
|
||||
Swing.runIfSwingOrRunLater(() -> refreshTab(target.getTrace()));
|
||||
}
|
||||
}
|
||||
|
||||
private final DebuggerThreadsPlugin plugin;
|
||||
private final DebuggerThreadsProvider provider;
|
||||
|
||||
// @AutoServiceConsumed by method
|
||||
DebuggerTargetService targetService;
|
||||
@AutoServiceConsumed
|
||||
@ -78,101 +70,80 @@ public class DebuggerTraceTabPanel extends HorizontalTabPanel<Trace>
|
||||
|
||||
private final SuppressableCallback<Void> cbCoordinateActivation = new SuppressableCallback<>();
|
||||
|
||||
private DebuggerTraceFileActionContext myActionContext;
|
||||
|
||||
public DebuggerTraceTabPanel(DebuggerThreadsProvider provider) {
|
||||
this.plugin = provider.plugin;
|
||||
this.provider = provider;
|
||||
|
||||
public DebuggerTraceTabPanel(Plugin plugin) {
|
||||
super("Trace");
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
|
||||
PluginTool tool = plugin.getTool();
|
||||
tool.addEventListener(TraceOpenedPluginEvent.class, this);
|
||||
tool.addEventListener(TraceActivatedPluginEvent.class, this);
|
||||
tool.addEventListener(TraceClosedPluginEvent.class, this);
|
||||
|
||||
list.setCellRenderer(new TabListCellRenderer<>() {
|
||||
protected String getText(Trace value) {
|
||||
return value.getName();
|
||||
}
|
||||
|
||||
protected Icon getIcon(Trace value) {
|
||||
if (targetService == null) {
|
||||
return super.getIcon(value);
|
||||
}
|
||||
Target target = targetService.getTarget(value);
|
||||
if (target == null || !target.isValid()) {
|
||||
return super.getIcon(value);
|
||||
}
|
||||
return DebuggerResources.ICON_RECORD;
|
||||
}
|
||||
});
|
||||
list.getSelectionModel().addListSelectionListener(this::traceTabSelected);
|
||||
list.addFocusListener(new FocusAdapter() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
setTraceTabActionContext(null);
|
||||
}
|
||||
});
|
||||
list.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
setTraceTabActionContext(e);
|
||||
}
|
||||
});
|
||||
setNameFunction(this::getNameForTrace);
|
||||
setIconFunction(this::getIconForTrace);
|
||||
setToolTipFunction(this::getTipForTrace);
|
||||
setSelectedTabConsumer(this::traceTabSelected);
|
||||
// Cannot use method ref here, since traceManager is still null
|
||||
setCloseTabConsumer(t -> traceManager.closeTrace(t));
|
||||
|
||||
actionCloseTrace = CloseTraceAction.builderPopup(plugin)
|
||||
.withContext(DebuggerTraceFileActionContext.class)
|
||||
.popupWhen(c -> c.getTrace() != null)
|
||||
.popupWhen(c -> {
|
||||
Trace trace = c.getTrace();
|
||||
if (trace == null) {
|
||||
return false;
|
||||
}
|
||||
actionCloseTrace.getPopupMenuData()
|
||||
.setMenuItemName(CloseTraceAction.NAME_PREFIX + getNameForTrace(trace));
|
||||
return true;
|
||||
})
|
||||
.onAction(c -> traceManager.closeTrace(c.getTrace()))
|
||||
.buildAndInstallLocal(provider);
|
||||
.buildAndInstall(tool);
|
||||
actionCloseAllTraces = CloseAllTracesAction.builderPopup(plugin)
|
||||
.withContext(DebuggerTraceFileActionContext.class)
|
||||
.popupWhen(c -> !traceManager.getOpenTraces().isEmpty())
|
||||
.onAction(c -> traceManager.closeAllTraces())
|
||||
.buildAndInstallLocal(provider);
|
||||
.buildAndInstall(tool);
|
||||
actionCloseOtherTraces = CloseOtherTracesAction.builderPopup(plugin)
|
||||
.withContext(DebuggerTraceFileActionContext.class)
|
||||
.popupWhen(c -> traceManager.getOpenTraces().size() > 1 && c.getTrace() != null)
|
||||
.onAction(c -> traceManager.closeOtherTraces(c.getTrace()))
|
||||
.buildAndInstallLocal(provider);
|
||||
.buildAndInstall(tool);
|
||||
actionCloseDeadTraces = CloseDeadTracesAction.builderPopup(plugin)
|
||||
.withContext(DebuggerTraceFileActionContext.class)
|
||||
.popupWhen(c -> !traceManager.getOpenTraces().isEmpty() && targetService != null)
|
||||
.onAction(c -> traceManager.closeDeadTraces())
|
||||
.buildAndInstallLocal(provider);
|
||||
.buildAndInstall(tool);
|
||||
}
|
||||
|
||||
private Trace computeClickedTraceTab(MouseEvent e) {
|
||||
JList<Trace> list = getList();
|
||||
int i = list.locationToIndex(e.getPoint());
|
||||
if (i < 0) {
|
||||
private String getNameForTrace(Trace trace) {
|
||||
return DomainObjectDisplayUtils.getTabText(trace);
|
||||
}
|
||||
|
||||
private Icon getIconForTrace(Trace trace) {
|
||||
if (targetService == null) {
|
||||
return null;
|
||||
}
|
||||
Rectangle cell = list.getCellBounds(i, i);
|
||||
if (!cell.contains(e.getPoint())) {
|
||||
Target target = targetService.getTarget(trace);
|
||||
if (target == null || !target.isValid()) {
|
||||
return null;
|
||||
}
|
||||
return getItem(i);
|
||||
return DebuggerResources.ICON_RECORD;
|
||||
}
|
||||
|
||||
private Trace setTraceTabActionContext(MouseEvent e) {
|
||||
Trace newTrace = e == null ? getSelectedItem() : computeClickedTraceTab(e);
|
||||
actionCloseTrace.getPopupMenuData()
|
||||
.setMenuItemName(
|
||||
CloseTraceAction.NAME_PREFIX + (newTrace == null ? "..." : newTrace.getName()));
|
||||
myActionContext = new DebuggerTraceFileActionContext(newTrace);
|
||||
provider.traceTabsContextChanged();
|
||||
return newTrace;
|
||||
private String getTipForTrace(Trace trace) {
|
||||
return DomainObjectDisplayUtils.getToolTip(trace);
|
||||
}
|
||||
|
||||
public DebuggerTraceFileActionContext getActionContext() {
|
||||
return myActionContext;
|
||||
}
|
||||
|
||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
||||
setSelectedItem(coordinates.getTrace());
|
||||
public DebuggerTraceFileActionContext getActionContext(MouseEvent e) {
|
||||
if (e == null) {
|
||||
return null;
|
||||
}
|
||||
Trace trace = getValueFor(e);
|
||||
if (trace == null) {
|
||||
return null;
|
||||
}
|
||||
return new DebuggerTraceFileActionContext(trace);
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
@ -186,28 +157,46 @@ public class DebuggerTraceTabPanel extends HorizontalTabPanel<Trace>
|
||||
}
|
||||
}
|
||||
|
||||
protected void add(Trace trace) {
|
||||
addTab(trace);
|
||||
trace.removeListener(this);
|
||||
trace.addListener(this);
|
||||
}
|
||||
|
||||
protected void remove(Trace trace) {
|
||||
trace.removeListener(this);
|
||||
removeTab(trace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventSent(PluginEvent event) {
|
||||
if (Objects.equals(event.getSourceName(), plugin.getName())) {
|
||||
return;
|
||||
}
|
||||
if (event instanceof TraceOpenedPluginEvent evt) {
|
||||
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
||||
addItem(evt.getTrace());
|
||||
add(evt.getTrace());
|
||||
}
|
||||
}
|
||||
else if (event instanceof TraceActivatedPluginEvent evt) {
|
||||
Trace trace = evt.getActiveCoordinates().getTrace();
|
||||
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
||||
selectTab(trace);
|
||||
}
|
||||
}
|
||||
else if (event instanceof TraceClosedPluginEvent evt) {
|
||||
Trace trace = evt.getTrace();
|
||||
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
||||
removeItem(evt.getTrace());
|
||||
remove(trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void traceTabSelected(ListSelectionEvent e) {
|
||||
if (e.getValueIsAdjusting()) {
|
||||
return;
|
||||
@Override
|
||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
||||
if (ev.getSource() instanceof Trace trace) {
|
||||
refreshTab(trace);
|
||||
}
|
||||
Trace newTrace = setTraceTabActionContext(null);
|
||||
}
|
||||
|
||||
private void traceTabSelected(Trace newTrace) {
|
||||
cbCoordinateActivation.invoke(() -> {
|
||||
traceManager.activateTrace(newTrace);
|
||||
});
|
@ -90,6 +90,12 @@ public class TraceRecorderTarget extends AbstractTarget {
|
||||
this.recorder = recorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describe() {
|
||||
return "%s in %s (recorder)".formatted(getTrace().getDomainFile().getName(),
|
||||
recorder.getTarget().getModel().getBrief());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return recorder.isRecording();
|
||||
|
@ -410,7 +410,7 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
|
||||
continue;
|
||||
}
|
||||
try (PeekOpenedDomainObject peek = new PeekOpenedDomainObject(df)) {
|
||||
if (programs.contains(peek.object)) {
|
||||
if (peek.object != null && programs.contains(peek.object)) {
|
||||
result.add(e);
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,13 @@ import java.util.stream.Stream;
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.*;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
import ghidra.app.plugin.core.debug.gui.control.DisconnectTask;
|
||||
import ghidra.app.plugin.core.debug.gui.control.TargetActionTask;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.DebuggerControlService.ControlModeChangeListener;
|
||||
import ghidra.async.*;
|
||||
@ -135,8 +138,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
|
||||
private void threadDeleted(TraceThread thread) {
|
||||
synchronized (listenersByTrace) {
|
||||
DebuggerCoordinates last = lastCoordsByTrace.get(trace);
|
||||
if (last != null && last.getThread() == thread) {
|
||||
LastCoords last = lastCoordsByTrace.get(trace);
|
||||
if (last != null && last.coords.getThread() == thread) {
|
||||
lastCoordsByTrace.remove(trace);
|
||||
}
|
||||
}
|
||||
@ -261,7 +264,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
protected final Map<Trace, DebuggerCoordinates> lastCoordsByTrace = new WeakHashMap<>();
|
||||
protected record LastCoords(Long time, DebuggerCoordinates coords) {
|
||||
|
||||
public static final LastCoords NEVER = new LastCoords(null, DebuggerCoordinates.NOWHERE);
|
||||
|
||||
public LastCoords(DebuggerCoordinates coords) {
|
||||
this(System.currentTimeMillis(), coords);
|
||||
}
|
||||
|
||||
public LastCoords keepTime(DebuggerCoordinates adjusted) {
|
||||
return new LastCoords(time, adjusted);
|
||||
}
|
||||
}
|
||||
|
||||
protected final Map<Trace, LastCoords> lastCoordsByTrace = new WeakHashMap<>();
|
||||
protected final Map<Trace, ListenerForTraceChanges> listenersByTrace = new WeakHashMap<>();
|
||||
protected final Set<Trace> tracesView = Collections.unmodifiableSet(listenersByTrace.keySet());
|
||||
|
||||
@ -275,6 +291,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
protected final AsyncReference<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true);
|
||||
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
||||
protected final AsyncReference<Boolean, Void> autoCloseOnTerminate = new AsyncReference<>(true);
|
||||
// Do not save this one, it's for testing only
|
||||
protected boolean ensureActiveTrace = true;
|
||||
|
||||
// @AutoServiceConsumed via method
|
||||
private DebuggerTargetService targetService;
|
||||
@ -431,37 +449,21 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
|
||||
@Override
|
||||
public void closeAllTraces() {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
for (Trace trace : getOpenTraces()) {
|
||||
closeTrace(trace);
|
||||
}
|
||||
});
|
||||
checkCloseTraces(getOpenTraces());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeOtherTraces(Trace keep) {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
for (Trace trace : getOpenTraces()) {
|
||||
if (trace != keep) {
|
||||
closeTrace(trace);
|
||||
}
|
||||
}
|
||||
});
|
||||
checkCloseTraces(getOpenTraces().stream().filter(t -> t != keep).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeDeadTraces() {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
if (targetService == null) {
|
||||
return;
|
||||
}
|
||||
for (Trace trace : getOpenTraces()) {
|
||||
Target target = targetService.getTarget(trace);
|
||||
if (target == null) {
|
||||
closeTrace(trace);
|
||||
}
|
||||
}
|
||||
});
|
||||
checkCloseTraces(targetService == null
|
||||
? getOpenTraces()
|
||||
: getOpenTraces().stream()
|
||||
.filter(t -> targetService.getTarget(t) == null)
|
||||
.toList());
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
@ -549,7 +551,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
current = newCurrent;
|
||||
if (newCurrent.getTrace() != null) {
|
||||
lastCoordsByTrace.put(newCurrent.getTrace(), newCurrent);
|
||||
lastCoordsByTrace.put(newCurrent.getTrace(), new LastCoords(newCurrent));
|
||||
}
|
||||
}
|
||||
contextChanged();
|
||||
@ -610,11 +612,10 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
if (!listenersByTrace.containsKey(trace)) {
|
||||
return;
|
||||
}
|
||||
DebuggerCoordinates cur =
|
||||
lastCoordsByTrace.getOrDefault(trace, DebuggerCoordinates.NOWHERE);
|
||||
LastCoords cur = lastCoordsByTrace.getOrDefault(trace, LastCoords.NEVER);
|
||||
DebuggerCoordinates adj =
|
||||
cur.platform(getPlatformForMapper(trace, cur.getObject(), mapper));
|
||||
lastCoordsByTrace.put(trace, adj);
|
||||
cur.coords.platform(getPlatformForMapper(trace, cur.coords.getObject(), mapper));
|
||||
lastCoordsByTrace.put(trace, cur.keepTime(adj));
|
||||
if (trace == current.getTrace()) {
|
||||
current = adj;
|
||||
fireLocationEvent(adj, ActivationCause.MAPPER_CHANGED);
|
||||
@ -670,7 +671,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
synchronized (listenersByTrace) {
|
||||
// If known, fill in target ASAP, so it determines the time
|
||||
return fillInTarget(trace,
|
||||
lastCoordsByTrace.getOrDefault(trace, DebuggerCoordinates.NOWHERE));
|
||||
lastCoordsByTrace.getOrDefault(trace, LastCoords.NEVER).coords);
|
||||
}
|
||||
}
|
||||
|
||||
@ -917,6 +918,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
|
||||
new TaskLauncher(new Task("Save New Trace", true, true, true) {
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
String filename = trace.getName();
|
||||
@ -958,6 +960,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
return future;
|
||||
@ -994,20 +997,64 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeTrace(Trace trace) {
|
||||
protected void doCloseTraces(Collection<Trace> traces, Collection<Target> targets) {
|
||||
for (Trace t : traces) {
|
||||
if (t.getConsumerList().contains(this)) {
|
||||
firePluginEvent(new TraceClosedPluginEvent(getName(), t));
|
||||
doTraceClosed(t);
|
||||
}
|
||||
}
|
||||
TargetActionTask.executeTask(tool, new DisconnectTask(tool, targets));
|
||||
}
|
||||
|
||||
protected static final String MSGPAT_TERMINATE = """
|
||||
<html>
|
||||
<body width="300px">
|
||||
<p>This will terminate the following targets:</p>
|
||||
<ul>
|
||||
%s
|
||||
</ul>
|
||||
<p>Proceed?</p>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
|
||||
protected static String formatTargets(Collection<Target> targets) {
|
||||
return targets.stream()
|
||||
.map(t -> "<li>%s</li>".formatted(HTMLUtilities.escapeHTML(t.describe())))
|
||||
.sorted()
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
protected void checkCloseTraces(Collection<Trace> traces) {
|
||||
List<Target> live =
|
||||
traces.stream()
|
||||
.map(t -> targetService.getTarget(t))
|
||||
.filter(t -> t != null)
|
||||
.toList();
|
||||
/**
|
||||
* A provider may be reading the trace, likely via the Swing thread, so schedule this on the
|
||||
* A provider may be reading a trace, likely via the Swing thread, so schedule this on the
|
||||
* same thread to avoid a ClosedException.
|
||||
*/
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
if (trace.getConsumerList().contains(this)) {
|
||||
firePluginEvent(new TraceClosedPluginEvent(getName(), trace));
|
||||
doTraceClosed(trace);
|
||||
if (live.isEmpty()) {
|
||||
doCloseTraces(traces, live);
|
||||
return;
|
||||
}
|
||||
String msg = MSGPAT_TERMINATE.formatted(formatTargets(live));
|
||||
int response = OptionDialog.showYesNoDialog(null, "Terminate", msg);
|
||||
switch (response) {
|
||||
case OptionDialog.YES_OPTION -> doCloseTraces(traces, live);
|
||||
case OptionDialog.NO_OPTION -> List.of();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeTrace(Trace trace) {
|
||||
checkCloseTraces(List.of(trace));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
super.dispose();
|
||||
@ -1034,6 +1081,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
return elem.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most recent coordinates among those traces still open
|
||||
*/
|
||||
protected DebuggerCoordinates getMostRecentCoordinates() {
|
||||
synchronized (listenersByTrace) {
|
||||
return lastCoordsByTrace.values()
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(l -> -l.time))
|
||||
.findFirst()
|
||||
.map(l -> l.coords)
|
||||
.orElse(DebuggerCoordinates.NOWHERE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
|
||||
ActivationCause cause) {
|
||||
@ -1048,6 +1109,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
throw new IllegalStateException(
|
||||
"Trace must be opened before activated: " + newTrace);
|
||||
}
|
||||
if (newTrace == null && ensureActiveTrace) {
|
||||
coordinates = getMostRecentCoordinates(); // Might still be NOWHERE
|
||||
}
|
||||
}
|
||||
|
||||
if (cause == ActivationCause.FOLLOW_PRESENT) {
|
||||
@ -1228,8 +1292,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
traces = tracesView.stream().filter(t -> {
|
||||
ProjectLocator loc = t.getDomainFile().getProjectLocator();
|
||||
return loc != null && !loc.isTransient();
|
||||
}).collect(Collectors.toList());
|
||||
coordsByTrace = Map.copyOf(lastCoordsByTrace);
|
||||
}).sorted(Comparator.comparingLong(t -> {
|
||||
LastCoords last = lastCoordsByTrace.get(t);
|
||||
return last == null ? -1 : last.time;
|
||||
})).toList();
|
||||
coordsByTrace = lastCoordsByTrace.entrySet()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().coords));
|
||||
}
|
||||
|
||||
saveState.putInt(KEY_TRACE_COUNT, traces.size());
|
||||
@ -1246,6 +1315,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
@Override
|
||||
public void readDataState(SaveState saveState) {
|
||||
synchronized (listenersByTrace) {
|
||||
long baseTime = System.currentTimeMillis();
|
||||
int traceCount = saveState.getInt(KEY_TRACE_COUNT, 0);
|
||||
for (int index = 0; index < traceCount; index++) {
|
||||
String stateName = PREFIX_OPEN_TRACE + index;
|
||||
@ -1253,7 +1323,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
DebuggerCoordinates coords =
|
||||
DebuggerCoordinates.readDataState(tool, saveState, stateName);
|
||||
if (coords.getTrace() != null) {
|
||||
lastCoordsByTrace.put(coords.getTrace(), coords);
|
||||
lastCoordsByTrace.put(coords.getTrace(),
|
||||
new LastCoords(baseTime + index, coords));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
/* ###
|
||||
* 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.listing;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.trace.DebuggerTraceTabPanel;
|
||||
|
||||
public class DebuggerListingProviderTestAccess {
|
||||
public static DebuggerTraceTabPanel getTraceTabs(DebuggerListingProvider provider) {
|
||||
return provider.traceTabs;
|
||||
}
|
||||
|
||||
}
|
@ -40,6 +40,7 @@ import ghidra.app.plugin.core.debug.gui.modules.DebuggerModuleMapProposalDialog.
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapModulesAction;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSectionsAction;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
|
||||
import ghidra.app.services.DebuggerListingService;
|
||||
import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry;
|
||||
import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry;
|
||||
@ -302,6 +303,7 @@ public class DebuggerModulesProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||
|
||||
@Test
|
||||
public void testActivatingNoTraceEmptiesProvider() throws Exception {
|
||||
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||
createAndOpenTrace();
|
||||
|
||||
addModules();
|
||||
|
@ -42,6 +42,7 @@ import ghidra.app.plugin.core.debug.gui.modules.DebuggerModuleMapProposalDialog.
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapModulesAction;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSectionsAction;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
|
||||
import ghidra.app.services.DebuggerListingService;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.schema.SchemaContext;
|
||||
@ -434,6 +435,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
|
||||
@Test
|
||||
public void testActivatingNoTraceEmptiesProvider() throws Exception {
|
||||
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||
createAndOpenTrace();
|
||||
|
||||
addModules();
|
||||
|
@ -17,9 +17,7 @@ package ghidra.app.plugin.core.debug.gui.thread;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@ -29,8 +27,8 @@ import db.Transaction;
|
||||
import generic.test.category.NightlyCategory;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.gui.thread.DebuggerLegacyThreadsPanel.ThreadTableColumns;
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
import ghidra.trace.model.time.TraceTimeManager;
|
||||
@ -60,32 +58,6 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that there exist no tabs, and that the tab row is invisible
|
||||
*/
|
||||
protected void assertZeroTabs() {
|
||||
assertEquals(0, threadsProvider.traceTabs.getList().getModel().getSize());
|
||||
assertEquals("Tab row should not be visible", 0,
|
||||
threadsProvider.traceTabs.getVisibleRect().height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that exactly one tab exists, and that the tab row is visible
|
||||
*/
|
||||
protected void assertOneTabPopulated() {
|
||||
assertEquals(1, threadsProvider.traceTabs.getList().getModel().getSize());
|
||||
assertNotEquals("Tab row should be visible", 0,
|
||||
threadsProvider.traceTabs.getVisibleRect().height);
|
||||
}
|
||||
|
||||
protected void assertNoTabSelected() {
|
||||
assertTabSelected(null);
|
||||
}
|
||||
|
||||
protected void assertTabSelected(Trace trace) {
|
||||
assertEquals(trace, threadsProvider.traceTabs.getSelectedItem());
|
||||
}
|
||||
|
||||
protected void assertThreadsEmpty() {
|
||||
List<ThreadRow> threadsDisplayed =
|
||||
threadsProvider.legacyPanel.threadTableModel.getModelData();
|
||||
@ -122,7 +94,6 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||
}
|
||||
|
||||
protected void assertProviderEmpty() {
|
||||
assertZeroTabs();
|
||||
assertThreadsEmpty();
|
||||
}
|
||||
|
||||
@ -132,45 +103,9 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||
assertProviderEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenTracePopupatesTab() throws Exception {
|
||||
createAndOpenTrace();
|
||||
waitForSwing();
|
||||
|
||||
assertOneTabPopulated();
|
||||
assertNoTabSelected();
|
||||
assertThreadsEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateTraceSelectsTab() throws Exception {
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertOneTabPopulated();
|
||||
assertTabSelected(tb.trace);
|
||||
|
||||
traceManager.activateTrace(null);
|
||||
waitForSwing();
|
||||
|
||||
assertOneTabPopulated();
|
||||
assertNoTabSelected();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectTabActivatesTrace() throws Exception {
|
||||
createAndOpenTrace();
|
||||
waitForSwing();
|
||||
threadsProvider.traceTabs.setSelectedItem(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(tb.trace, traceManager.getCurrentTrace());
|
||||
assertEquals(tb.trace, threadsProvider.current.getTrace());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateNoTraceEmptiesProvider() throws Exception {
|
||||
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||
createAndOpenTrace();
|
||||
addThreads();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
@ -184,22 +119,6 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||
assertThreadsEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentTraceClosedUpdatesTabs() throws Exception {
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertOneTabPopulated();
|
||||
assertTabSelected(tb.trace);
|
||||
|
||||
traceManager.closeTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertZeroTabs();
|
||||
assertNoTabSelected();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentTraceClosedEmptiesProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
@ -215,25 +134,6 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
||||
assertThreadsEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseTraceTabPopupMenuItem() throws Exception {
|
||||
createAndOpenTrace();
|
||||
waitForSwing();
|
||||
|
||||
assertOneTabPopulated(); // pre-check
|
||||
clickListItem(threadsProvider.traceTabs.getList(), 0, MouseEvent.BUTTON3);
|
||||
waitForSwing();
|
||||
Set<String> expected = Set.of("Close " + tb.trace.getName());
|
||||
assertMenu(expected, expected);
|
||||
|
||||
clickSubMenuItemByText("Close " + tb.trace.getName());
|
||||
waitForSwing();
|
||||
|
||||
waitForPass(() -> {
|
||||
assertEquals(Set.of(), traceManager.getOpenTraces());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateThenAddThreadsPopulatesProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
@ -17,10 +17,8 @@ package ghidra.app.plugin.core.debug.gui.thread;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.*;
|
||||
import org.junit.experimental.categories.Category;
|
||||
@ -31,6 +29,7 @@ import generic.test.category.NightlyCategory;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*;
|
||||
import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
|
||||
import ghidra.dbg.target.TargetExecutionStateful;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.schema.SchemaContext;
|
||||
@ -119,32 +118,6 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that there exist no tabs, and that the tab row is invisible
|
||||
*/
|
||||
protected void assertZeroTabs() {
|
||||
assertEquals(0, provider.traceTabs.getList().getModel().getSize());
|
||||
assertEquals("Tab row should not be visible", 0,
|
||||
provider.traceTabs.getVisibleRect().height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that exactly one tab exists, and that the tab row is visible
|
||||
*/
|
||||
protected void assertOneTabPopulated() {
|
||||
assertEquals(1, provider.traceTabs.getList().getModel().getSize());
|
||||
assertNotEquals("Tab row should be visible", 0,
|
||||
provider.traceTabs.getVisibleRect().height);
|
||||
}
|
||||
|
||||
protected void assertNoTabSelected() {
|
||||
assertTabSelected(null);
|
||||
}
|
||||
|
||||
protected void assertTabSelected(Trace trace) {
|
||||
assertEquals(trace, provider.traceTabs.getSelectedItem());
|
||||
}
|
||||
|
||||
protected void assertThreadsTableSize(int size) {
|
||||
assertEquals(size, provider.panel.getAllItems().size());
|
||||
}
|
||||
@ -195,7 +168,6 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
}
|
||||
|
||||
protected void assertProviderEmpty() {
|
||||
assertZeroTabs();
|
||||
assertThreadsEmpty();
|
||||
}
|
||||
|
||||
@ -218,53 +190,9 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
waitForPass(() -> assertProviderEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenTracePopupatesTab() throws Exception {
|
||||
createAndOpenTrace();
|
||||
waitForTasks();
|
||||
|
||||
waitForPass(() -> {
|
||||
assertOneTabPopulated();
|
||||
assertNoTabSelected();
|
||||
assertThreadsEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateTraceSelectsTab() throws Exception {
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForTasks();
|
||||
|
||||
waitForPass(() -> {
|
||||
assertOneTabPopulated();
|
||||
assertTabSelected(tb.trace);
|
||||
});
|
||||
|
||||
traceManager.activateTrace(null);
|
||||
waitForTasks();
|
||||
|
||||
waitForPass(() -> {
|
||||
assertOneTabPopulated();
|
||||
assertNoTabSelected();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectTabActivatesTrace() throws Exception {
|
||||
createAndOpenTrace();
|
||||
waitForTasks();
|
||||
provider.traceTabs.setSelectedItem(tb.trace);
|
||||
waitForTasks();
|
||||
|
||||
waitForPass(() -> {
|
||||
assertEquals(tb.trace, traceManager.getCurrentTrace());
|
||||
assertEquals(tb.trace, provider.current.getTrace());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateNoTraceEmptiesProvider() throws Exception {
|
||||
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||
createAndOpenTrace();
|
||||
addThreads();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
@ -278,26 +206,6 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
waitForPass(() -> assertThreadsEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentTraceClosedUpdatesTabs() throws Exception {
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForTasks();
|
||||
|
||||
waitForPass(() -> {
|
||||
assertOneTabPopulated();
|
||||
assertTabSelected(tb.trace);
|
||||
});
|
||||
|
||||
traceManager.closeTrace(tb.trace);
|
||||
waitForTasks();
|
||||
|
||||
waitForPass(() -> {
|
||||
assertZeroTabs();
|
||||
assertNoTabSelected();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentTraceClosedEmptiesProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
@ -313,25 +221,6 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
waitForPass(() -> assertThreadsEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseTraceTabPopupMenuItem() throws Exception {
|
||||
createAndOpenTrace();
|
||||
waitForTasks();
|
||||
|
||||
waitForPass(() -> assertOneTabPopulated());
|
||||
clickListItem(provider.traceTabs.getList(), 0, MouseEvent.BUTTON3);
|
||||
waitForTasks();
|
||||
Set<String> expected = Set.of("Close " + tb.trace.getName());
|
||||
assertMenu(expected, expected);
|
||||
|
||||
clickSubMenuItemByText("Close " + tb.trace.getName());
|
||||
waitForTasks();
|
||||
|
||||
waitForPass(() -> {
|
||||
assertEquals(Set.of(), traceManager.getOpenTraces());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateThenAddThreadsPopulatesProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
@ -0,0 +1,111 @@
|
||||
/* ###
|
||||
* 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.trace;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.*;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
|
||||
public class DebuggerTraceTabPanelTest extends AbstractGhidraHeadedDebuggerTest {
|
||||
private DebuggerTraceTabPanel traceTabs;
|
||||
|
||||
@Before
|
||||
public void setUpTabTest() throws Throwable {
|
||||
addPlugin(tool, DebuggerListingPlugin.class);
|
||||
DebuggerListingProvider listingProvider =
|
||||
waitForComponentProvider(DebuggerListingProvider.class);
|
||||
traceTabs = DebuggerListingProviderTestAccess.getTraceTabs(listingProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpty() {
|
||||
assertEquals(List.of(), traceTabs.getTabValues());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenTraceAddsTab() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(List.of(tb.trace), traceTabs.getTabValues());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateTraceSelectsTab() throws Throwable {
|
||||
try (
|
||||
ToyDBTraceBuilder tb1 = new ToyDBTraceBuilder(getName() + "_1", LANGID_TOYBE64);
|
||||
ToyDBTraceBuilder tb2 = new ToyDBTraceBuilder(getName() + "_2", LANGID_TOYBE64)) {
|
||||
traceManager.openTrace(tb1.trace);
|
||||
traceManager.openTrace(tb2.trace);
|
||||
waitForSwing();
|
||||
|
||||
traceManager.activateTrace(tb1.trace);
|
||||
waitForSwing();
|
||||
assertEquals(tb1.trace, traceTabs.getSelectedTabValue());
|
||||
|
||||
traceManager.activateTrace(tb2.trace);
|
||||
waitForSwing();
|
||||
assertEquals(tb2.trace, traceTabs.getSelectedTabValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectTabActivatesTrace() throws Throwable {
|
||||
try (
|
||||
ToyDBTraceBuilder tb1 = new ToyDBTraceBuilder(getName() + "_1", LANGID_TOYBE64);
|
||||
ToyDBTraceBuilder tb2 = new ToyDBTraceBuilder(getName() + "_2", LANGID_TOYBE64)) {
|
||||
traceManager.openTrace(tb1.trace);
|
||||
traceManager.openTrace(tb2.trace);
|
||||
waitForSwing();
|
||||
|
||||
traceTabs.selectTab(tb1.trace);
|
||||
waitForSwing();
|
||||
assertEquals(tb1.trace, traceManager.getCurrentTrace());
|
||||
|
||||
traceTabs.selectTab(tb2.trace);
|
||||
waitForSwing();
|
||||
assertEquals(tb2.trace, traceManager.getCurrentTrace());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseTraceRemovesTab() throws Throwable {
|
||||
try (
|
||||
ToyDBTraceBuilder tb1 = new ToyDBTraceBuilder(getName() + "_1", LANGID_TOYBE64);
|
||||
ToyDBTraceBuilder tb2 = new ToyDBTraceBuilder(getName() + "_2", LANGID_TOYBE64)) {
|
||||
traceManager.openTrace(tb1.trace);
|
||||
traceManager.openTrace(tb2.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(List.of(tb1.trace, tb2.trace), traceTabs.getTabValues());
|
||||
|
||||
traceManager.closeTrace(tb1.trace);
|
||||
waitForSwing();
|
||||
assertEquals(List.of(tb2.trace), traceTabs.getTabValues());
|
||||
|
||||
traceManager.closeTrace(tb2.trace);
|
||||
waitForSwing();
|
||||
assertEquals(List.of(), traceTabs.getTabValues());
|
||||
}
|
||||
}
|
||||
}
|
@ -46,6 +46,11 @@ class MockTarget implements Target {
|
||||
this.trace = trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describe() {
|
||||
return "Mock Target";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return true;
|
||||
|
@ -141,6 +141,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||
|
||||
assertEquals(thread, traceManager.getCurrentThread());
|
||||
|
||||
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||
traceManager.activateTrace(null);
|
||||
waitForSwing();
|
||||
|
||||
@ -177,6 +178,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||
|
||||
assertEquals(5, traceManager.getCurrentSnap());
|
||||
|
||||
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||
traceManager.activateTrace(null);
|
||||
waitForSwing();
|
||||
|
||||
@ -207,6 +209,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||
|
||||
assertEquals(5, traceManager.getCurrentFrame());
|
||||
|
||||
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||
traceManager.activateTrace(null);
|
||||
waitForSwing();
|
||||
|
||||
@ -251,6 +254,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
||||
assertEquals(objThread0, traceManager.getCurrentObject());
|
||||
assertEquals(thread, traceManager.getCurrentThread());
|
||||
|
||||
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||
traceManager.activateTrace(null);
|
||||
waitForSwing();
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
/* ###
|
||||
* 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.service.tracemgr;
|
||||
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
|
||||
public class DebuggerTraceManagerServiceTestAccess {
|
||||
public static void setEnsureActiveTrace(DebuggerTraceManagerService traceManager, boolean b) {
|
||||
((DebuggerTraceManagerServicePlugin) traceManager).ensureActiveTrace = b;
|
||||
}
|
||||
}
|
@ -1,202 +0,0 @@
|
||||
/* ###
|
||||
* 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 docking.widgets;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.BevelBorder;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
|
||||
// TODO: I'd like "close" buttons on the tabs, optionally.
|
||||
// For now, client must use a popup menu.
|
||||
@SuppressWarnings("serial")
|
||||
public class HorizontalTabPanel<T> extends JPanel {
|
||||
public static Color copyColor(Color c) {
|
||||
return new Color(c.getRGB());
|
||||
}
|
||||
|
||||
public static class TabListCellRenderer<T> implements ListCellRenderer<T> {
|
||||
protected final Box hBox = Box.createHorizontalBox();
|
||||
protected final JLabel label = new JLabel();
|
||||
|
||||
{
|
||||
hBox.setBorder(new BevelBorder(BevelBorder.RAISED));
|
||||
hBox.setOpaque(true);
|
||||
hBox.add(label);
|
||||
}
|
||||
|
||||
protected String getText(T value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
protected Icon getIcon(T value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends T> list,
|
||||
T value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
label.setText(getText(value));
|
||||
label.setIcon(getIcon(value));
|
||||
|
||||
if (isSelected) {
|
||||
//label.setForeground(list.getSelectionForeground());
|
||||
label.setForeground(copyColor(list.getSelectionForeground()));
|
||||
hBox.setBackground(list.getSelectionBackground());
|
||||
}
|
||||
else {
|
||||
label.setForeground(list.getForeground());
|
||||
hBox.setBackground(list.getBackground());
|
||||
}
|
||||
hBox.validate();
|
||||
return hBox;
|
||||
}
|
||||
}
|
||||
|
||||
protected final JList<T> list = new JList<>();
|
||||
private final JScrollPane scroll = new JScrollPane(list);
|
||||
private final JViewport viewport = scroll.getViewport();
|
||||
private final DefaultListModel<T> model = new DefaultListModel<>();
|
||||
private final JButton left = new JButton("<");
|
||||
private final JButton right = new JButton(">");
|
||||
|
||||
{
|
||||
list.setModel(model);
|
||||
// TODO: Experiment with multiple traces in one timeline
|
||||
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
|
||||
list.setVisibleRowCount(1);
|
||||
list.setCellRenderer(new TabListCellRenderer<>());
|
||||
list.setOpaque(false);
|
||||
|
||||
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
|
||||
scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
scroll.setBorder(null);
|
||||
|
||||
viewport.addChangeListener(this::viewportChanged);
|
||||
|
||||
left.setBorder(null);
|
||||
right.setBorder(null);
|
||||
left.setContentAreaFilled(false);
|
||||
right.setContentAreaFilled(false);
|
||||
left.setOpaque(true);
|
||||
right.setOpaque(true);
|
||||
left.addActionListener(this::leftActivated);
|
||||
right.addActionListener(this::rightActivated);
|
||||
}
|
||||
|
||||
public HorizontalTabPanel() {
|
||||
super();
|
||||
setLayout(new BorderLayout());
|
||||
list.setBackground(getBackground());
|
||||
add(scroll, BorderLayout.CENTER);
|
||||
add(left, BorderLayout.WEST);
|
||||
add(right, BorderLayout.EAST);
|
||||
}
|
||||
|
||||
private void viewportChanged(ChangeEvent e) {
|
||||
Dimension paneSize = getSize();
|
||||
Dimension listSize = list.getSize();
|
||||
boolean buttonsVisible = paneSize.getWidth() < listSize.getWidth();
|
||||
left.setVisible(buttonsVisible);
|
||||
right.setVisible(buttonsVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first cell which is even partially visible
|
||||
*
|
||||
* @param reverse true to search from right to left
|
||||
* @return the cell index
|
||||
*/
|
||||
private int findFirstVisible(boolean reverse) {
|
||||
int n = model.getSize();
|
||||
Rectangle vis = list.getVisibleRect();
|
||||
for (int i = reverse ? n - 1 : 0; reverse ? i >= 0 : i < n; i += reverse ? -1 : 1) {
|
||||
Rectangle b = list.getCellBounds(i, i);
|
||||
if (vis.intersects(b)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first cell <em>after</em> a given start which is even partially occluded
|
||||
*
|
||||
* @param start the starting cell index
|
||||
* @param reverse true to search from right to left
|
||||
* @return the cell index
|
||||
*/
|
||||
private int findNextOccluded(int start, boolean reverse) {
|
||||
if (start == -1) {
|
||||
return -1;
|
||||
}
|
||||
int n = model.getSize();
|
||||
Rectangle vis = list.getVisibleRect();
|
||||
for (int i = reverse ? start - 1 : start + 1; reverse ? i >= 0 : i < n; i +=
|
||||
reverse ? -1 : 1) {
|
||||
Rectangle b = list.getCellBounds(i, i);
|
||||
if (!vis.contains(b)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void leftActivated(ActionEvent e) {
|
||||
list.ensureIndexIsVisible(findNextOccluded(findFirstVisible(true), true));
|
||||
}
|
||||
|
||||
private void rightActivated(ActionEvent e) {
|
||||
list.ensureIndexIsVisible(findNextOccluded(findFirstVisible(false), false));
|
||||
}
|
||||
|
||||
public JList<T> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public void addItem(T item) {
|
||||
model.addElement(item);
|
||||
revalidate();
|
||||
}
|
||||
|
||||
public void removeItem(T item) {
|
||||
model.removeElement(item);
|
||||
revalidate();
|
||||
}
|
||||
|
||||
public T getSelectedItem() {
|
||||
int index = list.getSelectedIndex();
|
||||
return index < 0 ? null : list.getModel().getElementAt(index);
|
||||
}
|
||||
|
||||
public void setSelectedItem(T item) {
|
||||
// NOTE: For large lists, this could get slow
|
||||
int index = model.indexOf(item);
|
||||
if (index < 0) {
|
||||
list.clearSelection();
|
||||
}
|
||||
else {
|
||||
list.setSelectedIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
public T getItem(int index) {
|
||||
return list.getModel().getElementAt(index);
|
||||
}
|
||||
}
|
@ -222,51 +222,12 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener, Opti
|
||||
}
|
||||
}
|
||||
|
||||
String getStringUsedInList(Program program) {
|
||||
DomainFile df = program.getDomainFile();
|
||||
String changeIndicator = program.isChanged() ? "*" : "";
|
||||
String pathString = getShortPath(df);
|
||||
if (!df.isInWritableProject()) {
|
||||
return pathString + " [Read-Only]" + changeIndicator;
|
||||
}
|
||||
return pathString + changeIndicator;
|
||||
private String getToolTip(Program program) {
|
||||
return DomainObjectDisplayUtils.getToolTip(program);
|
||||
}
|
||||
|
||||
private String getShortPath(DomainFile df) {
|
||||
String pathString = df.toString();
|
||||
int length = pathString.length();
|
||||
if (length < 100) {
|
||||
return pathString;
|
||||
}
|
||||
|
||||
String[] pathParts = pathString.split("/");
|
||||
if (pathParts.length == 2) { // at least 2 for project name and filename
|
||||
return pathString;
|
||||
}
|
||||
|
||||
String projectName = df.getProjectLocator().getName();
|
||||
int parentFolderIndex = pathParts.length - 2;
|
||||
String parentName = pathParts[parentFolderIndex];
|
||||
String filename = df.getName();
|
||||
pathString = projectName + ":/.../" + parentName + "/" + filename;
|
||||
return pathString;
|
||||
}
|
||||
|
||||
String getToolTip(Program program) {
|
||||
return getStringUsedInList(program);
|
||||
}
|
||||
|
||||
String getName(Program program) {
|
||||
DomainFile df = program.getDomainFile();
|
||||
String tabName = df.getName();
|
||||
if (df.isReadOnly()) {
|
||||
int version = df.getVersion();
|
||||
if (!df.canSave() && version != DomainFile.DEFAULT_VERSION) {
|
||||
tabName += "@" + version;
|
||||
}
|
||||
tabName = tabName + " [Read-Only]";
|
||||
}
|
||||
return tabName;
|
||||
private String getTabName(Program program) {
|
||||
return DomainObjectDisplayUtils.getTabText(program);
|
||||
}
|
||||
|
||||
void keyTypedFromListWindow(KeyEvent e) {
|
||||
@ -334,22 +295,6 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener, Opti
|
||||
return EMPTY8_ICON;
|
||||
}
|
||||
|
||||
private String getTabName(Program program) {
|
||||
DomainFile df = program.getDomainFile();
|
||||
String tabName = df.getName();
|
||||
if (df.isReadOnly()) {
|
||||
int version = df.getVersion();
|
||||
if (!df.canSave() && version != DomainFile.DEFAULT_VERSION) {
|
||||
tabName += "@" + version;
|
||||
}
|
||||
tabName = tabName + " [Read-Only]";
|
||||
}
|
||||
if (program.isChanged()) {
|
||||
tabName = "*" + tabName;
|
||||
}
|
||||
return tabName;
|
||||
}
|
||||
|
||||
boolean removeProgram(Program program) {
|
||||
return progService.closeProgram(program, false);
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
/* ###
|
||||
* 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.framework.model;
|
||||
|
||||
import ghidra.framework.store.FileSystem;
|
||||
|
||||
public class DomainObjectDisplayUtils {
|
||||
private static final String VERSION_SEP = "@";
|
||||
private static final String CHANGE_INDICATOR = "*";
|
||||
private static final String READ_ONLY = " [Read-Only]";
|
||||
private static final String PROJECT_SEP_ELLIPSES =
|
||||
":" + FileSystem.SEPARATOR + "..." + FileSystem.SEPARATOR;
|
||||
|
||||
private DomainObjectDisplayUtils() {
|
||||
}
|
||||
|
||||
public static String getShortPath(DomainFile df) {
|
||||
String pathString = df.toString();
|
||||
int length = pathString.length();
|
||||
if (length < 100) {
|
||||
return pathString;
|
||||
}
|
||||
|
||||
String[] pathParts = pathString.split(FileSystem.SEPARATOR);
|
||||
if (pathParts.length == 2) { // at least 2 for project name and filename
|
||||
return pathString;
|
||||
}
|
||||
|
||||
String projectName = df.getProjectLocator().getName();
|
||||
int parentFolderIndex = pathParts.length - 2;
|
||||
String parentName = pathParts[parentFolderIndex];
|
||||
String filename = df.getName();
|
||||
pathString =
|
||||
projectName + PROJECT_SEP_ELLIPSES + parentName + FileSystem.SEPARATOR + filename;
|
||||
return pathString;
|
||||
}
|
||||
|
||||
public static String getToolTip(DomainObject object) {
|
||||
DomainFile df = object.getDomainFile();
|
||||
String changeIndicator = object.isChanged() ? CHANGE_INDICATOR : "";
|
||||
String pathString = getShortPath(df);
|
||||
if (!df.isInWritableProject()) {
|
||||
return pathString + READ_ONLY + changeIndicator;
|
||||
}
|
||||
return pathString + changeIndicator;
|
||||
}
|
||||
|
||||
public static String getTabText(DomainFile df) {
|
||||
String tabName = df.getName();
|
||||
if (df.isReadOnly()) {
|
||||
int version = df.getVersion();
|
||||
if (!df.canSave() && version != DomainFile.DEFAULT_VERSION) {
|
||||
tabName += VERSION_SEP + version;
|
||||
}
|
||||
tabName = tabName + READ_ONLY;
|
||||
}
|
||||
return tabName;
|
||||
}
|
||||
|
||||
public static String getTabText(DomainObject object) {
|
||||
if (object.isChanged()) {
|
||||
return CHANGE_INDICATOR + getTabText(object.getDomainFile());
|
||||
}
|
||||
return getTabText(object.getDomainFile());
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import org.junit.Test;
|
||||
|
||||
import db.Transaction;
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServiceTestAccess;
|
||||
import ghidra.app.script.GhidraState;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
|
||||
@ -172,6 +173,7 @@ public class DeadFlatDebuggerAPITest extends AbstractFlatDebuggerAPITest<FlatDeb
|
||||
|
||||
@Test
|
||||
public void testActivateTraceNull() throws Throwable {
|
||||
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
@ -217,6 +219,7 @@ public class DeadFlatDebuggerAPITest extends AbstractFlatDebuggerAPITest<FlatDeb
|
||||
|
||||
@Test
|
||||
public void testActivateThreadNull() throws Throwable {
|
||||
DebuggerTraceManagerServiceTestAccess.setEnsureActiveTrace(traceManager, false);
|
||||
api.activateThread(null);
|
||||
assertEquals(null, traceManager.getCurrentThread());
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user