From 04d2e88c2d1fab9b6275e45a4e6e043c4740a282 Mon Sep 17 00:00:00 2001
From: Dan <46821332+nsadeveloper789@users.noreply.github.com>
Date: Wed, 3 Apr 2024 16:02:06 -0400
Subject: [PATCH] GP-1608: DebuggerListing use GTabPanel. No tabs in Threads.
---
.../java/ghidra/debug/api/target/Target.java | 7 +
.../api/tracemgr/DebuggerCoordinates.java | 10 +
.../service/tracermi/TraceRmiTarget.java | 6 +
.../core/debug/gui/DebuggerResources.java | 6 +-
.../gui/control/DebuggerControlPlugin.java | 16 +-
.../debug/gui/control/DisconnectTask.java | 53 +++++
.../gui/listing/DebuggerListingProvider.java | 27 ++-
.../gui/thread/DebuggerThreadsPanel.java | 17 +-
.../gui/thread/DebuggerThreadsProvider.java | 21 +-
.../DebuggerTraceTabPanel.java | 169 +++++++--------
.../service/model/TraceRecorderTarget.java | 6 +
.../service/modules/ProgramModuleIndexer.java | 2 +-
.../DebuggerTraceManagerServicePlugin.java | 153 +++++++++----
.../DebuggerListingProviderTestAccess.java | 25 +++
.../DebuggerModulesProviderLegacyTest.java | 2 +
.../modules/DebuggerModulesProviderTest.java | 2 +
.../DebuggerThreadsProviderLegacyTest.java | 104 +--------
.../thread/DebuggerThreadsProviderTest.java | 115 +---------
.../gui/trace/DebuggerTraceTabPanelTest.java | 111 ++++++++++
.../debug/service/control/MockTarget.java | 5 +
.../DebuggerTraceManagerServiceTest.java | 4 +
...DebuggerTraceManagerServiceTestAccess.java | 24 +++
.../docking/widgets/HorizontalTabPanel.java | 202 ------------------
.../plugin/core/progmgr/MultiTabPlugin.java | 63 +-----
.../model/DomainObjectDisplayUtils.java | 79 +++++++
.../flatapi/DeadFlatDebuggerAPITest.java | 3 +
26 files changed, 579 insertions(+), 653 deletions(-)
create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DisconnectTask.java
rename Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/{thread => trace}/DebuggerTraceTabPanel.java (57%)
create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTestAccess.java
create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanelTest.java
create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTestAccess.java
delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/HorizontalTabPanel.java
create mode 100644 Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectDisplayUtils.java
diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java
index 041c3aecd3..e24b9be390 100644
--- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java
+++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java
@@ -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
*
diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java
index 76b2e8bef2..c65e205133 100644
--- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java
+++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java
@@ -45,6 +45,16 @@ import ghidra.util.NotOwnerException;
public class DebuggerCoordinates {
+ /**
+ * Coordinates that indicate no trace is active in the Debugger UI.
+ *
+ *
+ * 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);
diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java
index cf79ddc269..6efb3a138e 100644
--- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java
+++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java
@@ -81,6 +81,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);
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java
index 855d6be8c2..2bf97cffae 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java
@@ -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";
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java
index 216097b4a7..b4ab401c7b 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java
@@ -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() {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DisconnectTask.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DisconnectTask.java
new file mode 100644
index 0000000000..638900a138
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DisconnectTask.java
@@ -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 targets;
+
+ public DisconnectTask(PluginTool tool, Collection 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);
+ }
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
index 8233abb76d..057d532cb3 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
@@ -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 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.
*/
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java
index c398fa740f..628fb4923a 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java
@@ -77,6 +77,14 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel {
public ThreadPcColumn() {
super(Address.class);
@@ -85,7 +93,8 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel 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() {
@@ -111,7 +120,7 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel 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() {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java
index 216f208c81..d83043533c 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java
@@ -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() {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceTabPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanel.java
similarity index 57%
rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceTabPanel.java
rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanel.java
index ce3977fd95..46f66b1f3f 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceTabPanel.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanel.java
@@ -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
- implements PluginEventListener {
+public class DebuggerTraceTabPanel extends GTabPanel
+ 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
private final SuppressableCallback 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 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
}
}
+ 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);
});
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java
index 2f94f2df87..6dd37d91b5 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java
@@ -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();
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java
index 20e8230328..5b0e3580d3 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java
@@ -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);
}
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
index e1a3438da5..653d736710 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
@@ -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 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 lastCoordsByTrace = new WeakHashMap<>();
protected final Map listenersByTrace = new WeakHashMap<>();
protected final Set tracesView = Collections.unmodifiableSet(listenersByTrace.keySet());
@@ -275,6 +291,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
protected final AsyncReference saveTracesByDefault = new AsyncReference<>(true);
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference 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 traces, Collection 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 = """
+
+
+ This will terminate the following targets:
+
+ Proceed?
+
+
+ """;
+
+ protected static String formatTargets(Collection targets) {
+ return targets.stream()
+ .map(t -> "%s".formatted(HTMLUtilities.escapeHTML(t.describe())))
+ .sorted()
+ .collect(Collectors.joining("\n"));
+ }
+
+ protected void checkCloseTraces(Collection traces) {
+ List 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 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));
}
}
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTestAccess.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTestAccess.java
new file mode 100644
index 0000000000..e144bd0d64
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTestAccess.java
@@ -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;
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java
index f5ad46b758..a5b1a8b299 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderLegacyTest.java
@@ -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();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java
index 7754328a67..923ab2b4ec 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java
@@ -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();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java
index c74611ac31..d8e77594fa 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java
@@ -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 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 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();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java
index f655fcb87f..c02ef6692e 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java
@@ -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 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();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanelTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanelTest.java
new file mode 100644
index 0000000000..ea1bf923ef
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanelTest.java
@@ -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());
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/MockTarget.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/MockTarget.java
index 4c43781d7d..40fe1bcdf7 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/MockTarget.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/MockTarget.java
@@ -46,6 +46,11 @@ class MockTarget implements Target {
this.trace = trace;
}
+ @Override
+ public String describe() {
+ return "Mock Target";
+ }
+
@Override
public boolean isValid() {
return true;
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java
index c9d5830177..2d4199b23e 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java
@@ -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();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTestAccess.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTestAccess.java
new file mode 100644
index 0000000000..43e8740e82
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTestAccess.java
@@ -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;
+ }
+}
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/HorizontalTabPanel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/HorizontalTabPanel.java
deleted file mode 100644
index d7a8ac8254..0000000000
--- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/HorizontalTabPanel.java
+++ /dev/null
@@ -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 extends JPanel {
- public static Color copyColor(Color c) {
- return new Color(c.getRGB());
- }
-
- public static class TabListCellRenderer implements ListCellRenderer {
- 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 list = new JList<>();
- private final JScrollPane scroll = new JScrollPane(list);
- private final JViewport viewport = scroll.getViewport();
- private final DefaultListModel 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 after 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 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);
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java
index e8a99fd060..bc14648244 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java
@@ -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);
}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectDisplayUtils.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectDisplayUtils.java
new file mode 100644
index 0000000000..5aa1e002f4
--- /dev/null
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectDisplayUtils.java
@@ -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());
+ }
+}
diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/debug/flatapi/DeadFlatDebuggerAPITest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/debug/flatapi/DeadFlatDebuggerAPITest.java
index 55df0404a0..e0a2a4b2e6 100644
--- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/debug/flatapi/DeadFlatDebuggerAPITest.java
+++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/debug/flatapi/DeadFlatDebuggerAPITest.java
@@ -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