diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html
index c0e0f69b3b..4c2cfc02a2 100644
--- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html
+++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html
@@ -41,12 +41,15 @@
dead or terminated. A "dead" trace can still be manipulated and marked up, but it will not
record any new target information.
+ Close Trace / All / Other / Dead
+ Traces
+
In most cases, a trace is ephemeral, but occasionally, interesting behavior is observed that
- is difficult to store as static mark-up. When a trace is no longer needed, it can be closed by
- right-clicking the tab and selecting "Close Trace." Warning: closing a
- trace that has not been saved cannot be undone. If you accumulate many unwanted
- traces, use one of the "Close Others," "Close Dead," or "Close All" actions from the pop-up
- menu.
+ is difficult to store as static mark-up. When traces are no longer needed, they can be closed
+ by right-clicking a tab and selecting one of the actions. See Trace
+ Management for details of each action.
Navigating Threads
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 dba8c1420d..f6637fb2c7 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
@@ -1748,26 +1748,36 @@ public interface DebuggerResources {
.menuIcon(ICON)
.menuPath(DebuggerPluginPackage.NAME, NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
-
}
}
interface CloseTraceAction {
String NAME_PREFIX = "Close ";
- String DESCRIPTION = "Close the current trace";
+ String DESCRIPTION = "Close the current or selected trace";
String GROUP = GROUP_TRACE_CLOSE;
+ String SUB_GROUP = "a";
Icon ICON = ICON_CLOSE;
String HELP_ANCHOR = "close_trace";
- static ActionBuilder builder(Plugin owner) {
+ static ActionBuilder builderCommon(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME_PREFIX, ownerName)
.description(DESCRIPTION)
- .menuGroup(GROUP)
- .menuIcon(ICON)
- .menuPath(DebuggerPluginPackage.NAME, NAME_PREFIX + "...")
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
+ }
+ static ActionBuilder builder(Plugin owner) {
+ return builderCommon(owner)
+ .menuGroup(GROUP, SUB_GROUP)
+ .menuIcon(ICON)
+ .menuPath(DebuggerPluginPackage.NAME, NAME_PREFIX + "...");
+ }
+
+ static ActionBuilder builderPopup(Plugin owner) {
+ return builderCommon(owner)
+ .popupMenuGroup(GROUP, SUB_GROUP)
+ .popupMenuIcon(ICON)
+ .popupMenuPath(NAME_PREFIX + "...");
}
}
@@ -1776,14 +1786,25 @@ public interface DebuggerResources {
String DESCRIPTION = "Close all traces";
String HELP_ANCHOR = "close_all_traces";
- static ActionBuilder builder(Plugin owner) {
+ static ActionBuilder builderCommon(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
+ .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
+ }
+
+ static ActionBuilder builder(Plugin owner) {
+ return builderCommon(owner)
.menuGroup(GROUP)
.menuIcon(ICON)
- .menuPath(DebuggerPluginPackage.NAME, NAME)
- .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
+ .menuPath(DebuggerPluginPackage.NAME, NAME);
+ }
+
+ static ActionBuilder builderPopup(Plugin owner) {
+ return builderCommon(owner)
+ .popupMenuGroup(GROUP)
+ .popupMenuIcon(ICON)
+ .popupMenuPath(NAME);
}
}
@@ -1792,14 +1813,25 @@ public interface DebuggerResources {
String DESCRIPTION = "Close all traces except the current one";
String HELP_ANCHOR = "close_other_traces";
- static ActionBuilder builder(Plugin owner) {
+ static ActionBuilder builderCommon(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
+ .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
+ }
+
+ static ActionBuilder builder(Plugin owner) {
+ return builderCommon(owner)
.menuGroup(GROUP)
.menuIcon(ICON)
- .menuPath(DebuggerPluginPackage.NAME, NAME)
- .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
+ .menuPath(DebuggerPluginPackage.NAME, NAME);
+ }
+
+ static ActionBuilder builderPopup(Plugin owner) {
+ return builderCommon(owner)
+ .popupMenuGroup(GROUP)
+ .popupMenuIcon(ICON)
+ .popupMenuPath(NAME);
}
}
@@ -1808,14 +1840,25 @@ public interface DebuggerResources {
String DESCRIPTION = "Close all traces not being recorded";
String HELP_ANCHOR = "close_dead_traces";
- static ActionBuilder builder(Plugin owner) {
+ static ActionBuilder builderCommon(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
+ .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
+ }
+
+ static ActionBuilder builder(Plugin owner) {
+ return builderCommon(owner)
.menuGroup(GROUP)
.menuIcon(ICON)
- .menuPath(DebuggerPluginPackage.NAME, NAME)
- .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
+ .menuPath(DebuggerPluginPackage.NAME, NAME);
+ }
+
+ static ActionBuilder builderPopup(Plugin owner) {
+ return builderCommon(owner)
+ .popupMenuGroup(GROUP)
+ .popupMenuIcon(ICON)
+ .popupMenuPath(NAME);
}
}
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 177e2caa2b..4c9cb5fd0f 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
@@ -15,11 +15,11 @@
*/
package ghidra.app.plugin.core.debug.gui.thread;
-import java.awt.*;
+import java.awt.BorderLayout;
+import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
-import java.util.List;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
@@ -361,6 +361,12 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
SeekTracePresentAction actionSeekTracePresent;
ToggleDockingAction actionSyncFocus;
DockingAction actionGoToTime;
+
+ DockingAction actionCloseTrace;
+ DockingAction actionCloseOtherTraces;
+ DockingAction actionCloseDeadTraces;
+ DockingAction actionCloseAllTraces;
+
Set strongRefs = new HashSet<>(); // Eww
public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) {
@@ -554,6 +560,11 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
threadTable.getSelectionModel().addListSelectionListener(this::threadRowSelected);
threadTable.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ setThreadRowActionContext();
+ }
+
@Override
public void mouseReleased(MouseEvent e) {
int selectedRow = threadTable.getSelectedRow();
@@ -584,15 +595,13 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
list.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
- checkTraceTabPopupViaMouse(e);
+ setTraceTabActionContext(e);
}
@Override
public void mouseReleased(MouseEvent e) {
- checkTraceTabPopupViaMouse(e);
}
});
- // TODO: The popup key? Only seems to have rawCode=0x93 (147) in Swing
mainPanel.add(traceTabs, BorderLayout.NORTH);
TableColumnModel columnModel = threadTable.getColumnModel();
@@ -622,53 +631,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
});
}
- private void checkTraceTabPopupViaMouse(MouseEvent e) {
- if (!e.isPopupTrigger()) {
- return;
- }
- JList list = traceTabs.getList();
- int i = list.locationToIndex(e.getPoint());
- if (i < 0) {
- return;
- }
- Rectangle cell = list.getCellBounds(i, i);
- if (!cell.contains(e.getPoint())) {
- return;
- }
- showTraceTabPopup(e.getComponent(), e.getPoint(), i);
- }
-
- private void showTraceTabPopup(Component comp, Point p, int i) {
- // TODO: Make this use action contexts and docking actions instead
- traceTabPopupMenu.removeAll();
- final Trace trace = traceTabs.getItem(i);
- JMenuItem closeItem =
- new JMenuItem("Close " + trace.getName(), DebuggerResources.ICON_CLOSE);
- closeItem.addActionListener(evt -> {
- traceManager.closeTrace(trace);
- });
- JMenuItem closeOthers = new JMenuItem("Close Others", DebuggerResources.ICON_CLOSE);
- closeOthers.addActionListener(evt -> {
- traceManager.closeOtherTraces(trace);
- });
- JMenuItem closeDead = new JMenuItem("Close Dead", DebuggerResources.ICON_CLOSE);
- closeDead.addActionListener(evt -> {
- traceManager.closeDeadTraces();
- });
- JMenuItem closeAll = new JMenuItem("Close All", DebuggerResources.ICON_CLOSE);
- closeAll.addActionListener(evt -> {
- for (Trace t : List.copyOf(traceManager.getOpenTraces())) {
- traceManager.closeTrace(t);
- }
- });
- traceTabPopupMenu.add(closeItem);
- traceTabPopupMenu.addSeparator();
- traceTabPopupMenu.add(closeOthers);
- traceTabPopupMenu.add(closeDead);
- traceTabPopupMenu.add(closeAll);
- traceTabPopupMenu.show(comp, p.x, p.y);
- }
-
protected void createActions() {
// TODO: Make other actions use builder?
actionStepSnapBackward = new StepSnapBackwardAction();
@@ -687,6 +649,27 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
.buildAndInstallLocal(this);
traceManager.addSynchronizeFocusChangeListener(
strongRef(new ToToggleSelectionListener(actionSyncFocus)));
+
+ actionCloseTrace = CloseTraceAction.builderPopup(plugin)
+ .withContext(DebuggerTraceFileActionContext.class)
+ .popupWhen(c -> c.getTrace() != null)
+ .onAction(c -> traceManager.closeTrace(c.getTrace()))
+ .buildAndInstallLocal(this);
+ actionCloseAllTraces = CloseAllTracesAction.builderPopup(plugin)
+ .withContext(DebuggerTraceFileActionContext.class)
+ .popupWhen(c -> !traceManager.getOpenTraces().isEmpty())
+ .onAction(c -> traceManager.closeAllTraces())
+ .buildAndInstallLocal(this);
+ actionCloseOtherTraces = CloseOtherTracesAction.builderPopup(plugin)
+ .withContext(DebuggerTraceFileActionContext.class)
+ .popupWhen(c -> traceManager.getOpenTraces().size() > 1 && c.getTrace() != null)
+ .onAction(c -> traceManager.closeOtherTraces(c.getTrace()))
+ .buildAndInstallLocal(this);
+ actionCloseDeadTraces = CloseDeadTracesAction.builderPopup(plugin)
+ .withContext(DebuggerTraceFileActionContext.class)
+ .popupWhen(c -> !traceManager.getOpenTraces().isEmpty() && modelService != null)
+ .onAction(c -> traceManager.closeDeadTraces())
+ .buildAndInstallLocal(this);
}
private void toggleSyncFocus(boolean enabled) {
@@ -712,24 +695,50 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
}
+ private Trace computeClickedTraceTab(MouseEvent e) {
+ JList list = traceTabs.getList();
+ int i = list.locationToIndex(e.getPoint());
+ if (i < 0) {
+ return null;
+ }
+ Rectangle cell = list.getCellBounds(i, i);
+ if (!cell.contains(e.getPoint())) {
+ return null;
+ }
+ return traceTabs.getItem(i);
+ }
+
+ private Trace setTraceTabActionContext(MouseEvent e) {
+ Trace newTrace = e == null ? traceTabs.getSelectedItem() : computeClickedTraceTab(e);
+ actionCloseTrace.getPopupMenuData()
+ .setMenuItemName(
+ CloseTraceAction.NAME_PREFIX + (newTrace == null ? "..." : newTrace.getName()));
+ myActionContext = new DebuggerTraceFileActionContext(newTrace);
+ contextChanged();
+ return newTrace;
+ }
+
private void traceTabSelected(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
- Trace newTrace = traceTabs.getSelectedItem();
- myActionContext = new DebuggerThreadActionContext(newTrace, null);
- contextChanged();
+ Trace newTrace = setTraceTabActionContext(null);
cbCoordinateActivation.invoke(() -> traceManager.activateTrace(newTrace));
}
+ private ThreadRow setThreadRowActionContext() {
+ ThreadRow row = threadFilterPanel.getSelectedItem();
+ myActionContext = new DebuggerThreadActionContext(current.getTrace(),
+ row == null ? null : row.getThread());
+ contextChanged();
+ return row;
+ }
+
private void threadRowSelected(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
- ThreadRow row = threadFilterPanel.getSelectedItem();
- myActionContext = new DebuggerThreadActionContext(current.getTrace(),
- row == null ? null : row.getThread());
- contextChanged();
+ ThreadRow row = setThreadRowActionContext();
if (row != null && traceManager != null) {
cbCoordinateActivation.invoke(() -> traceManager.activateThread(row.getThread()));
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceFileActionContext.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceFileActionContext.java
new file mode 100644
index 0000000000..4a926a3b79
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceFileActionContext.java
@@ -0,0 +1,31 @@
+/* ###
+ * 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.thread;
+
+import docking.ActionContext;
+import ghidra.trace.model.Trace;
+
+public class DebuggerTraceFileActionContext extends ActionContext {
+ private final Trace trace;
+
+ public DebuggerTraceFileActionContext(Trace trace) {
+ this.trace = trace;
+ }
+
+ public Trace getTrace() {
+ return trace;
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramURLUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramURLUtils.java
index 639117d7a5..ea5c0df68d 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramURLUtils.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramURLUtils.java
@@ -23,6 +23,7 @@ import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.program.model.listing.Program;
+import ghidra.util.Msg;
public enum ProgramURLUtils {
;
@@ -33,7 +34,9 @@ public enum ProgramURLUtils {
if (projectLocator == null) {
return null;
}
- RepositoryAdapter repository = file.getParent().getProjectData().getRepository();
+ DomainFolder parent = file.getParent();
+ ProjectData projectData = parent == null ? null : parent.getProjectData();
+ RepositoryAdapter repository = projectData == null ? null : projectData.getRepository();
if (repository != null) { // There is an associated remote repo
if (file.isVersioned()) { // The domain file exists there
ServerInfo server = repository.getServerInfo();
@@ -66,6 +69,11 @@ public enum ProgramURLUtils {
}
URL localProjURL = new URL(asString.substring(0, bangLoc));
ProjectData projectData = project.getProjectData(localProjURL);
+ if (projectData == null) {
+ Msg.error(ProgramURLUtils.class, "The repository containing " + ghidraURL +
+ " is not open in the Project manager.");
+ return null;
+ }
return projectData.getFile(asString.substring(bangLoc + 1));
}
catch (MalformedURLException e) {