Patterned on the Program Overview, this plugin provides a pair of sidebars for the
+ Dynamic Listing that indicate the history of the current trace, similar to those in the
+ Time
+ and Memview plugins.
+ The Trace Overview bar gives a compressed view of various events
+ (thread creation/destruction, module loads/unloads, et cetera). Events are added in time
+ snap order without spaces between consecutive events, as in the Memview display. The Trace
+ Selection sidebar allows the user to click & drag over a section of either sidebar and zooms
+ in on that time span. In the zoomed version, events are in snap order with intervening space
+ to indicate the actual time delays (although these are dictated, in part, by the numbering
+ schema for events).
+
+
+
+
Navigation
+
+
Zoom
+
+
Clicking and dragging over a region of the Trace Overview causes that
+ span to be displayed uncompressed in the Trace Selection. The same action can also
+ be applied to the Trace Selection itself, resulting in a more detailed zoom.
+
+
Move
+
+
Shift click & drag on the Trace Selection allows the user to move forward and
+ backward in the view without rescaling.
+
+
+
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorComponent.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorComponent.java
new file mode 100644
index 0000000000..a07d3841c6
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorComponent.java
@@ -0,0 +1,251 @@
+/* ###
+ * 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.timeoverview;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.List;
+import java.util.TreeSet;
+
+import javax.swing.*;
+
+import docking.action.DockingActionIf;
+import ghidra.app.nav.Navigatable;
+import ghidra.app.util.viewer.listingpanel.OverviewProvider;
+import ghidra.app.util.viewer.util.AddressIndexMap;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.Program;
+import ghidra.trace.model.Lifespan;
+import ghidra.util.task.SwingUpdateManager;
+
+/**
+ * Overview bar component. Uses color to indicate various snap-based properties for a program.
+ * Uses an {@link TimeOverviewColorService} to get the appropriate color for a snaps.
+ */
+public class TimeOverviewColorComponent extends JPanel implements OverviewProvider {
+ private static final Color DEFAULT_COLOR = Color.GRAY;
+ protected TimeOverviewColorService service;
+ private Color[] colorsByPixel = new Color[0];
+ private final SwingUpdateManager refreshUpdater =
+ new SwingUpdateManager(100, 15000, () -> doRefresh());
+
+ private PluginTool tool;
+ private List actions;
+ private TimeOverviewColorPlugin plugin;
+
+ /**
+ * Constructor
+ *
+ * @param tool the PluginTool
+ * @param overviewColorService the {@link TimeOverviewColorService} that provides colors for
+ * various snaps.
+ */
+ public TimeOverviewColorComponent(PluginTool tool,
+ TimeOverviewColorService overviewColorService) {
+ this.tool = tool;
+ this.service = overviewColorService;
+ overviewColorService.setOverviewComponent(this);
+ addMouseListener(new MouseAdapter() {
+ private int pressedY;
+ private boolean enableDrag = false;
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON1) {
+ Long snap = service.getSnap(e.getY());
+ gotoSnap(snap);
+ }
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ enableDrag = true;
+ pressedY = e.getY();
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (enableDrag) {
+ Long start = service.getSnap(pressedY);
+ Long stop = service.getSnap(e.getY());
+ if (start == null || stop == null) {
+ return;
+ }
+ Lifespan span;
+ if ((e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0) {
+ Lifespan prev = getLifespan();
+ if (prev != null) {
+ int shift = stop.intValue() - start.intValue();
+ span = Lifespan.span(prev.lmin() - shift, prev.lmax() - shift);
+ }
+ else {
+ span = prev;
+ }
+ }
+ else {
+ if (start > stop) {
+ Long tmp = stop;
+ stop = start;
+ start = tmp;
+ }
+ span = Lifespan.span(start, stop);
+ }
+ plugin.setLifespan(span);
+ enableDrag = false;
+ }
+ }
+
+ });
+ ToolTipManager.sharedInstance().registerComponent(this);
+ actions = service.getActions();
+ }
+
+ /**
+ * Installs actions for this component
+ */
+ public void installActions() {
+ if (actions == null) {
+ return;
+ }
+ for (DockingActionIf action : actions) {
+ tool.addAction(action);
+ }
+ }
+
+ /**
+ * Removes previous installed actions for this component.
+ */
+ public void uninstallActions() {
+ if (actions == null) {
+ return;
+ }
+ for (DockingActionIf action : actions) {
+ tool.removeAction(action);
+ }
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(16, 1);
+ }
+
+ protected void gotoSnap(Long snap) {
+ plugin.gotoSnap(snap);
+ }
+
+ @Override
+ public String getToolTipText(MouseEvent e) {
+ Long snap = service.getSnap(e.getY());
+ return service.getToolTipText(snap);
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ int width = getWidth();
+ int pixelCount = getOverviewPixelCount();
+
+ g.setColor(getBackground());
+ g.fillRect(0, 0, width - 1, getHeight() - 1);
+
+ if (service.getTrace() == null) {
+ return;
+ }
+
+ for (int i = 0; i < pixelCount; i++) {
+ Color color = getColor(i);
+ g.setColor(color);
+ g.fillRect(1, i, width - 3, 1);
+ }
+ if (colorsByPixel.length != pixelCount) {
+ colorsByPixel = new Color[pixelCount];
+ refreshUpdater.updateLater();
+ }
+ }
+
+ private Color getColor(int index) {
+ if (colorsByPixel != null && index < colorsByPixel.length) {
+ return colorsByPixel[index];
+ }
+ return DEFAULT_COLOR;
+ }
+
+ public int getOverviewPixelCount() {
+ return Math.max(getHeight(), 0);
+ }
+
+ private void doRefresh() {
+ for (int i = 0; i < colorsByPixel.length; i++) {
+ if (colorsByPixel[i] == null) {
+ Long snap = service.getSnap(i);
+ colorsByPixel[i] = service.getColor(snap);
+ }
+ }
+ repaint();
+ }
+
+ @Override
+ public JComponent getComponent() {
+ return this;
+ }
+
+ public void setLifeSet(TreeSet set) {
+ service.setIndices(set);
+ colorsByPixel = new Color[getOverviewPixelCount()];
+ refreshUpdater.updateLater();
+ }
+
+ /**
+ * Causes this component to completely compute the colors used to paint the overview bar.
+ */
+ public void refreshAll() {
+ colorsByPixel = new Color[getOverviewPixelCount()];
+ refreshUpdater.updateLater();
+ }
+
+ /**
+ * Returns the PluginTool
+ *
+ * @return the PluginTool
+ */
+ public PluginTool getTool() {
+ return tool;
+ }
+
+ public void setPlugin(TimeOverviewColorPlugin plugin) {
+ this.plugin = plugin;
+ service.setPlugin(plugin);
+ }
+
+ public Lifespan getLifespan() {
+ return service.getBounds();
+ }
+
+ public void setLifespan(Lifespan bounds) {
+ service.setBounds(bounds);
+ }
+
+ @Override
+ public void setProgram(Program program, AddressIndexMap map) {
+ // Ignored
+ }
+
+ @Override
+ public void setNavigatable(Navigatable navigatable) {
+ // Ignored
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorPlugin.java
new file mode 100644
index 0000000000..6ff5707e75
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorPlugin.java
@@ -0,0 +1,374 @@
+/* ###
+ * 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.timeoverview;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import javax.swing.SwingUtilities;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+import docking.ActionContext;
+import docking.action.*;
+import docking.menu.MultiActionDockingAction;
+import ghidra.app.plugin.PluginCategoryNames;
+import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
+import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
+import ghidra.app.plugin.core.debug.event.*;
+import ghidra.app.plugin.core.debug.gui.timeoverview.timetype.TimeType;
+import ghidra.app.services.DebuggerListingService;
+import ghidra.app.services.DebuggerTraceManagerService;
+import ghidra.debug.api.tracemgr.DebuggerCoordinates;
+import ghidra.framework.options.SaveState;
+import ghidra.framework.plugintool.*;
+import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
+import ghidra.framework.plugintool.util.PluginStatus;
+import ghidra.trace.model.Lifespan;
+import ghidra.trace.model.Trace;
+import ghidra.trace.model.bookmark.TraceBookmark;
+import ghidra.trace.model.bookmark.TraceBookmarkManager;
+import ghidra.trace.model.breakpoint.*;
+import ghidra.trace.model.memory.*;
+import ghidra.trace.model.modules.*;
+import ghidra.trace.model.target.TraceObject;
+import ghidra.trace.model.thread.*;
+import ghidra.util.HelpLocation;
+import ghidra.util.Msg;
+import ghidra.util.classfinder.ClassSearcher;
+import resources.ResourceManager;
+
+/**
+ * Plugin to manage {@link TimeOverviewColorService}s. It creates actions for each service and
+ * installs and removes {@link TimeOverviewColorComponent} as indicated by the action.
+ */
+
+@PluginInfo(
+ status = PluginStatus.UNSTABLE,
+ packageName = DebuggerPluginPackage.NAME,
+ category = PluginCategoryNames.DEBUGGER,
+ shortDescription = "Time Overview Color Manager",
+ description = "Provides various color mappings for the trace snap space.",
+ eventsConsumed = {
+ TraceOpenedPluginEvent.class, //
+ TraceClosedPluginEvent.class, //
+ TraceActivatedPluginEvent.class, //
+ }, //
+ servicesRequired = { //
+ DebuggerTraceManagerService.class, //
+ } //
+)
+
+public class TimeOverviewColorPlugin extends AbstractDebuggerPlugin {
+
+ @AutoServiceConsumed
+ private DebuggerTraceManagerService traceManager;
+ @AutoServiceConsumed
+ private DebuggerListingService listingService;
+ @SuppressWarnings("unused")
+ private final AutoService.Wiring autoServiceWiring;
+
+
+ public static final String HELP_TOPIC = "OverviewPlugin";
+ private static final String ACTIVE_SERVICES = "ActiveServices";
+ private List allServices;
+ private Map activeServices =
+ new LinkedHashMap<>(); // maintain the left to right order of the active overview bars.
+ private Map actionMap = new HashMap<>();
+ private MultiActionDockingAction multiAction;
+
+ private Trace currentTrace;
+ private final TimeOverviewEventListener eventListener = new TimeOverviewEventListener(this);
+ private Map> sets = new WeakHashMap<>();
+ private Map>> types = new HashMap<>();
+ private long LMAX = Lifespan.ALL.lmax();
+
+ public TimeOverviewColorPlugin(PluginTool tool) {
+ super(tool);
+ autoServiceWiring = AutoService.wireServicesConsumed(tool, this);
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ allServices = ClassSearcher.getInstances(TimeOverviewColorService.class);
+ createActions();
+ for (TimeOverviewColorService service : allServices) {
+ service.initialize(tool);
+ }
+ }
+
+ @Override
+ public void readConfigState(SaveState saveState) {
+ String[] activeServiceNames = saveState.getStrings(ACTIVE_SERVICES, new String[0]);
+ for (String serviceName : activeServiceNames) {
+ TimeOverviewColorService service = getService(serviceName);
+ if (service == null) {
+ Msg.warn(this, "Can't restore TimeOverviewColorService: " + serviceName);
+ continue;
+ }
+ OverviewToggleAction action = actionMap.get(service);
+ action.setSelected(true);
+ // do this later so that they show up to the left of the standard marker service overview.
+ SwingUtilities.invokeLater(() -> installOverview(service));
+ }
+ }
+
+ private TimeOverviewColorService getService(String serviceName) {
+ for (TimeOverviewColorService service : allServices) {
+ if (service.getName().equals(serviceName)) {
+ return service;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void cleanup() {
+
+ List services = new ArrayList<>(activeServices.keySet());
+ for (TimeOverviewColorService service : services) {
+ uninstallOverview(service);
+ }
+ listingService.removeLocalAction(multiAction);
+ }
+
+ @Override
+ public void writeConfigState(SaveState saveState) {
+ saveState.putStrings(ACTIVE_SERVICES, getActiveServiceNames());
+ }
+
+ private String[] getActiveServiceNames() {
+ List names =
+ activeServices.keySet().stream().map(s -> s.getName()).collect(Collectors.toList());
+
+ return names.toArray(new String[names.size()]);
+ }
+
+ private void createActions() {
+ for (TimeOverviewColorService overviewColorService : allServices) {
+ actionMap.put(overviewColorService,
+ new OverviewToggleAction(getName(), overviewColorService));
+ }
+ multiAction = new MultiActionDockingAction("TimeOverview", getName());
+ //multiAction.setPerformActionOnButtonClick(false);
+ multiAction.setActions(new ArrayList(actionMap.values()));
+ multiAction.setToolBarData(
+ new ToolBarData(ResourceManager.loadImage("images/x-office-document-template.png")));
+ listingService.addLocalAction(multiAction);
+ multiAction.setDescription("Toggles trace overview margin displays.");
+ multiAction.setHelpLocation(
+ new HelpLocation(TimeOverviewColorPlugin.HELP_TOPIC,
+ TimeOverviewColorPlugin.HELP_TOPIC));
+
+ }
+
+ private class OverviewToggleAction extends ToggleDockingAction {
+
+ private TimeOverviewColorService service;
+
+ public OverviewToggleAction(String owner, TimeOverviewColorService service) {
+ super(service.getName(), owner);
+ this.service = service;
+ setMenuBarData(new MenuData(new String[] { "Show " + service.getName() }));
+ setHelpLocation(service.getHelpLocation());
+ }
+
+ @Override
+ public void actionPerformed(ActionContext context) {
+ if (isSelected()) {
+ installOverview(service);
+ }
+ else {
+ uninstallOverview(service);
+ }
+ }
+
+ }
+
+ /**
+ * Installs the given {@link TimeOverviewColorService} into the Listing margin bars. This is
+ * public only for testing and screenshot purposes.
+ *
+ * @param overviewColorService the service to display colors in the Listing's margin bars.
+ */
+ public void installOverview(TimeOverviewColorService overviewColorService) {
+ overviewColorService.setTrace(currentTrace);
+ TimeOverviewColorComponent overview =
+ new TimeOverviewColorComponent(tool, overviewColorService);
+ activeServices.put(overviewColorService, overview);
+ listingService.addOverviewProvider(overview);
+ overview.installActions();
+ overview.setPlugin(this);
+ }
+
+ private void uninstallOverview(TimeOverviewColorService overviewColorService) {
+ TimeOverviewColorComponent overviewComponent = activeServices.get(overviewColorService);
+ overviewComponent.uninstallActions();
+ listingService.removeOverviewProvider(overviewComponent);
+ activeServices.remove(overviewColorService);
+ overviewColorService.setTrace(null);
+ }
+
+ protected void traceActivated(Trace trace) {
+ if (trace != null && trace != currentTrace) {
+ if (currentTrace != null) {
+ currentTrace.removeListener(eventListener);
+ }
+ currentTrace = trace;
+ currentTrace.addListener(eventListener);
+ for (TimeOverviewColorService service : activeServices.keySet()) {
+ service.setTrace(trace);
+ }
+ updateMap();
+ }
+ }
+
+ protected void traceDeactivated(Trace trace) {
+ if (trace == currentTrace) {
+ currentTrace.removeListener(eventListener);
+ currentTrace = null;
+ for (TimeOverviewColorService service : activeServices.keySet()) {
+ service.setTrace(null);
+ }
+ }
+ }
+
+ @Override
+ public void processEvent(PluginEvent event) {
+ super.processEvent(event);
+ if (event instanceof TraceActivatedPluginEvent ev) {
+ DebuggerCoordinates coordinates = ev.getActiveCoordinates();
+ traceActivated(coordinates.getTrace());
+ eventListener.coordinatesActivated(coordinates);
+ }
+ else if (event instanceof TraceClosedPluginEvent ev) {
+ Trace trace = ev.getTrace();
+ if (trace == currentTrace) {
+ sets.remove(trace);
+ traceDeactivated(trace);
+ }
+ }
+ }
+
+ void updateMap() {
+ TreeSet set = new TreeSet<>();
+ Trace trace = traceManager.getCurrentTrace();
+ TraceThreadManager threadManager = trace.getThreadManager();
+ for (TraceThread thread : threadManager.getAllThreads()) {
+ if (thread instanceof TraceObjectThread objThread) {
+ addObject(set, objThread.getObject());
+ }
+ }
+ TraceModuleManager moduleManager = trace.getModuleManager();
+ for (TraceModule module : moduleManager.getAllModules()) {
+ if (module instanceof TraceObjectModule objModule) {
+ addObject(set, objModule.getObject());
+ }
+ }
+ TraceMemoryManager memoryManager = trace.getMemoryManager();
+ for (TraceMemoryRegion region : memoryManager.getAllRegions()) {
+ if (region instanceof TraceObjectMemoryRegion objRegion) {
+ addObject(set, objRegion.getObject());
+ }
+ }
+ TraceBreakpointManager breakpointManager = trace.getBreakpointManager();
+ for (TraceBreakpoint bpt : breakpointManager.getAllBreakpoints()) {
+ if (bpt instanceof TraceObjectBreakpointLocation objBreakpoint) {
+ addObject(set, objBreakpoint.getObject());
+ }
+ }
+ TraceBookmarkManager bookmarkManager = trace.getBookmarkManager();
+ for (TraceBookmark mark : bookmarkManager.getAllBookmarks()) {
+ Lifespan span = mark.getLifespan();
+ set.add(span.min());
+ if (span.lmax() == LMAX) {
+ set.add(span.max());
+ }
+ }
+ for (TimeOverviewColorComponent provider : activeServices.values()) {
+ provider.setLifeSet(set);
+ }
+ }
+
+ private void addObject(TreeSet set, TraceObject obj) {
+ for (Lifespan span : obj.getLife().spans()) {
+ set.add(span.min());
+ if (span.lmax() == LMAX) {
+ set.add(span.max());
+ }
+ }
+ }
+
+ void updateMap(long offset, TimeType type, String desc, boolean override) {
+ TreeSet set = getLifeSet();
+ if (!override && set.contains(offset)) {
+ return;
+ }
+ if (offset == LMAX) {
+ return;
+ }
+ set.add(offset);
+ setLifespanType(offset, type, desc);
+ for (TimeOverviewColorComponent provider : activeServices.values()) {
+ provider.setLifeSet(set);
+ }
+ }
+
+ TreeSet getLifeSet() {
+ TreeSet set = sets.get(currentTrace);
+ if (set == null) {
+ set = new TreeSet<>();
+ sets.put(currentTrace, set);
+ }
+ return set;
+ }
+
+ /**
+ * Determines the {@link TimeType} for the given offset
+ *
+ * @param offset the offset for which to get an LifespanType.
+ * @return the {@link TimeType} for the given offset.
+ */
+ public Set> getTypes(Long offset) {
+ Set> set = types.get(offset);
+ if (set == null) {
+ set = new HashSet<>();
+ }
+ return set;
+ }
+
+ void setLifespanType(Long offset, TimeType type, String desc) {
+ Set> set = getTypes(offset);
+ set.add(new ImmutablePair(type, desc));
+ types.put(offset, set);
+ }
+
+ public void gotoSnap(Long offset) {
+ if (offset == null) {
+ offset = 0L;
+ }
+ traceManager.activateSnap(offset);
+ }
+
+ public void setLifespan(Lifespan span) {
+ for (TimeOverviewColorComponent provider : activeServices.values()) {
+ provider.setLifespan(span);
+ }
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorService.java
new file mode 100644
index 0000000000..f310bfd6e2
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewColorService.java
@@ -0,0 +1,139 @@
+/* ###
+ * 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.timeoverview;
+
+import java.awt.Color;
+import java.util.*;
+
+import docking.action.DockingActionIf;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.trace.model.Lifespan;
+import ghidra.trace.model.Trace;
+import ghidra.util.HelpLocation;
+import ghidra.util.classfinder.ExtensionPoint;
+
+/**
+ * Interface for services that know how to associate colors with any snap in a program. Instances
+ * of these services are discovered and presented as options on the Listing's right margin area.
+ */
+public interface TimeOverviewColorService extends ExtensionPoint {
+
+ /**
+ * Returns the name of this color service.
+ *
+ * @return the name of this color service.
+ */
+ public String getName();
+
+ /**
+ * Returns the color that this service associates with the given snap.
+ *
+ * @param snap the snap to convert to a color.
+ * @return the color that this service associates with the given snap.
+ */
+ public Color getColor(Long snap);
+
+ /**
+ * Sets the trace that this service will provide snap colors for.
+ *
+ * @param trace the program that this service will provide snap colors for.
+ */
+ public void setTrace(Trace trace);
+
+ /**
+ * Sets the component that will be displaying the colors for this
+ * service.
+ *
+ * @param component the {@link TimeOverviewColorComponent} that will be displaying the colors
+ * for this service.
+ */
+ public void setOverviewComponent(TimeOverviewColorComponent component);
+
+ /**
+ * Returns the tool tip that the {@link TimeOverviewColorComponent} should display when the
+ * mouse is hovering on the pixel that maps to the given snap.
+ *
+ * @param snap the snap for which to get a tooltip.
+ * @return the tooltip text for the given snap.
+ */
+ public String getToolTipText(Long snap);
+
+ /**
+ * Returns a list of popup actions to be shown when the user right-clicks on the
+ * {@link TimeOverviewColorComponent} associated with this service.
+ *
+ * @return the list of popup actions.
+ */
+ public List getActions();
+
+ /**
+ * Returns the {@link HelpLocation} for this service
+ *
+ * @return the {@link HelpLocation} for this service
+ */
+ public HelpLocation getHelpLocation();
+
+ /**
+ * Initialize the service which typically is used to read options for the service.
+ *
+ * @param tool the {@link PluginTool} using this service.
+ */
+ public void initialize(PluginTool tool);
+
+ /**
+ * Returns the current trace used by the service.
+ *
+ * @return the current trace used by the service.
+ */
+ public Trace getTrace();
+
+ /**
+ * Set the plugin
+ *
+ * @param plugin overview plugin
+ */
+ public void setPlugin(TimeOverviewColorPlugin plugin);
+
+ /**
+ * Get the snap for a given pixel's time coordinate
+ *
+ * @param pixel location in the display
+ * @return snap
+ */
+ public Long getSnap(int pixel);
+
+ /**
+ * Set the indices for mapping pixels->indices->snaps (and vice-versa)
+ *
+ * @param set tree-set of snaps
+ */
+ public void setIndices(TreeSet set);
+
+ /**
+ * Get the display bounds
+ *
+ * @return bounds time-range to display
+ */
+ public Lifespan getBounds();
+
+ /**
+ * Set the display bounds
+ *
+ * @param bounds time-range to display
+ */
+ public void setBounds(Lifespan bounds);
+
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewEventListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewEventListener.java
new file mode 100644
index 0000000000..9a7b278f0f
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/TimeOverviewEventListener.java
@@ -0,0 +1,329 @@
+/* ###
+ * 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.timeoverview;
+
+import java.util.Objects;
+
+import ghidra.app.plugin.core.debug.gui.timeoverview.timetype.TimeType;
+import ghidra.debug.api.tracemgr.DebuggerCoordinates;
+import ghidra.framework.model.DomainObjectChangeRecord;
+import ghidra.framework.model.DomainObjectEvent;
+import ghidra.trace.model.*;
+import ghidra.trace.model.bookmark.TraceBookmark;
+import ghidra.trace.model.breakpoint.*;
+import ghidra.trace.model.memory.*;
+import ghidra.trace.model.modules.*;
+import ghidra.trace.model.target.TraceObject;
+import ghidra.trace.model.thread.*;
+import ghidra.trace.util.TraceEvents;
+import ghidra.util.Swing;
+
+public class TimeOverviewEventListener extends TraceDomainObjectListener {
+
+ private TimeOverviewColorPlugin p;
+ private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
+
+ public TimeOverviewEventListener(TimeOverviewColorPlugin plugin) {
+
+ this.p = plugin;
+
+ listenForUntyped(DomainObjectEvent.RESTORED, this::objectRestored);
+
+ listenFor(TraceEvents.THREAD_ADDED, this::threadAdded);
+ listenFor(TraceEvents.THREAD_CHANGED, this::threadChanged);
+ listenFor(TraceEvents.THREAD_LIFESPAN_CHANGED, this::threadChanged);
+ listenFor(TraceEvents.THREAD_DELETED, this::threadDeleted);
+
+ listenFor(TraceEvents.MODULE_ADDED, this::moduleAdded);
+ listenFor(TraceEvents.MODULE_CHANGED, this::moduleChanged);
+ listenFor(TraceEvents.MODULE_LIFESPAN_CHANGED, this::moduleChanged);
+ listenFor(TraceEvents.MODULE_DELETED, this::moduleDeleted);
+
+ listenFor(TraceEvents.REGION_ADDED, this::regionAdded);
+ listenFor(TraceEvents.REGION_CHANGED, this::regionChanged);
+ listenFor(TraceEvents.REGION_LIFESPAN_CHANGED, this::regionChanged);
+ listenFor(TraceEvents.REGION_DELETED, this::regionDeleted);
+
+ listenFor(TraceEvents.BREAKPOINT_ADDED, this::bptAdded);
+ listenFor(TraceEvents.BREAKPOINT_CHANGED, this::bptChanged);
+ listenFor(TraceEvents.BREAKPOINT_LIFESPAN_CHANGED, this::bptChanged);
+ listenFor(TraceEvents.BREAKPOINT_DELETED, this::bptDeleted);
+
+ listenFor(TraceEvents.BOOKMARK_ADDED, this::bookmarkAdded);
+ listenFor(TraceEvents.BOOKMARK_CHANGED, this::bookmarkChanged);
+ listenFor(TraceEvents.BOOKMARK_LIFESPAN_CHANGED, this::bookmarkChanged);
+ listenFor(TraceEvents.BOOKMARK_DELETED, this::bookmarkDeleted);
+
+ }
+
+ public void coordinatesActivated(DebuggerCoordinates coordinates) {
+ //DebuggerCoordinates adjusted = adjustCoordinates(coordinates);
+ setCoordinates(coordinates);
+ Trace trace = coordinates.getTrace();
+ if (trace != null) {
+ Swing.runLater(() -> processTrace(trace));
+ }
+ }
+
+ protected void addListener() {
+ Trace trace = current.getTrace();
+ if (trace != null) {
+ trace.addListener(this);
+ }
+ }
+
+ protected void removeListener() {
+ Trace trace = current.getTrace();
+ if (trace != null) {
+ trace.removeListener(this);
+ }
+ }
+
+ public void setCoordinates(DebuggerCoordinates coordinates) {
+ boolean doListeners = !Objects.equals(current.getTrace(), coordinates.getTrace());
+ if (doListeners) {
+ removeListener();
+ }
+ current = coordinates;
+ if (doListeners) {
+ addListener();
+ }
+ }
+
+ private void processTrace(Trace trace) {
+ //updateList.clear();
+ TraceThreadManager threadManager = trace.getThreadManager();
+ for (TraceThread thread : threadManager.getAllThreads()) {
+ threadChanged(thread);
+ }
+ TraceModuleManager moduleManager = trace.getModuleManager();
+ for (TraceModule module : moduleManager.getAllModules()) {
+ moduleChanged(module);
+ }
+ TraceMemoryManager memoryManager = trace.getMemoryManager();
+ for (TraceMemoryRegion region : memoryManager.getAllRegions()) {
+ regionChanged(region);
+ }
+ TraceBreakpointManager breakpointManager = trace.getBreakpointManager();
+ for (TraceBreakpoint bpt : breakpointManager.getAllBreakpoints()) {
+ bptChanged(bpt);
+ }
+ }
+
+ private void threadAdded(TraceThread thread) {
+ if (!(thread instanceof TraceObjectThread objThread)) {
+ return;
+ }
+
+ TraceObject obj = objThread.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
+ .forEach(v -> {
+ long snap = v.getMinSnap();
+ p.updateMap(snap, TimeType.BPT_ADDED, thread.getName(snap), true);
+ });
+ }
+
+ private void threadChanged(TraceThread thread) {
+ if (!(thread instanceof TraceObjectThread objThread)) {
+ return;
+ }
+
+ TraceObject obj = objThread.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectThread.KEY_TID, true)
+ .forEach(v -> {
+ long snapMin = v.getMinSnap();
+ long snapMax = v.getMaxSnap();
+ if (snapMin == snapMax) {
+ p.updateMap(snapMin, TimeType.THREAD_CHANGED, thread.getName(snapMin), true);
+ }
+ else {
+ p.updateMap(snapMin, TimeType.THREAD_ADDED, thread.getName(snapMin), true);
+ p.updateMap(snapMax, TimeType.THREAD_REMOVED, thread.getName(snapMax), true);
+ }
+ });
+ }
+
+ private void threadDeleted(TraceThread thread) {
+ if (!(thread instanceof TraceObjectThread objThread)) {
+ return;
+ }
+
+ TraceObject obj = objThread.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
+ .forEach(v -> {
+ long snap = v.getMaxSnap();
+ p.updateMap(snap, TimeType.THREAD_REMOVED, thread.getName(snap), true);
+ });
+ }
+
+ private void moduleAdded(TraceModule module) {
+ if (!(module instanceof TraceObjectModule objMod)) {
+ return;
+ }
+
+ TraceObject obj = objMod.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
+ .forEach(v -> {
+ long snap = v.getMinSnap();
+ p.updateMap(snap, TimeType.MODULE_ADDED, module.getName(snap), true);
+ });
+ }
+
+ private void moduleChanged(TraceModule module) {
+ if (!(module instanceof TraceObjectModule objMod)) {
+ return;
+ }
+
+ TraceObject obj = objMod.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
+ .forEach(v -> {
+ long snapMin = v.getMinSnap();
+ long snapMax = v.getMaxSnap();
+ if (snapMin == snapMax) {
+ p.updateMap(snapMin, TimeType.MODULE_CHANGED, module.getName(snapMin), true);
+ }
+ else {
+ p.updateMap(snapMin, TimeType.MODULE_ADDED, module.getName(snapMin), true);
+ p.updateMap(snapMax, TimeType.MODULE_REMOVED, module.getName(snapMax), true);
+ }
+ });
+ }
+
+ private void moduleDeleted(TraceModule module) {
+ if (!(module instanceof TraceObjectModule objMod)) {
+ return;
+ }
+
+ TraceObject obj = objMod.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
+ .forEach(v -> {
+ long snap = v.getMaxSnap();
+ p.updateMap(snap, TimeType.MODULE_REMOVED, module.getName(snap), true);
+ });
+ }
+
+ private void regionAdded(TraceMemoryRegion region) {
+ if (!(region instanceof TraceObjectMemoryRegion objReg)) {
+ return;
+ }
+
+ TraceObject obj = objReg.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
+ .forEach(v -> {
+ long snap = v.getMinSnap();
+ p.updateMap(snap, TimeType.REGION_ADDED, region.getName(snap), true);
+ });
+ }
+
+ private void regionChanged(TraceMemoryRegion region) {
+ if (!(region instanceof TraceObjectMemoryRegion objReg)) {
+ return;
+ }
+
+ TraceObject obj = objReg.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
+ .forEach(v -> {
+ long snapMin = v.getMinSnap();
+ long snapMax = v.getMaxSnap();
+ if (snapMin == snapMax) {
+ p.updateMap(snapMin, TimeType.REGION_CHANGED, region.getName(snapMin), true);
+ }
+ else {
+ p.updateMap(snapMin, TimeType.REGION_ADDED, region.getName(snapMin), true);
+ p.updateMap(snapMax, TimeType.REGION_REMOVED, region.getName(snapMax), true);
+ }
+ });
+ }
+
+ private void regionDeleted(TraceMemoryRegion region) {
+ if (!(region instanceof TraceObjectMemoryRegion objReg)) {
+ return;
+ }
+
+ TraceObject obj = objReg.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
+ .forEach(v -> {
+ long snap = v.getMaxSnap();
+ p.updateMap(snap, TimeType.REGION_REMOVED, region.getName(snap), true);
+ });
+ }
+
+ private void bptAdded(TraceBreakpoint bpt) {
+ if (!(bpt instanceof TraceObjectBreakpointLocation objBpt)) {
+ return;
+ }
+
+ TraceObject obj = objBpt.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
+ .forEach(v -> {
+ long snap = v.getMinSnap();
+ p.updateMap(snap, TimeType.BPT_ADDED, bpt.getName(snap), true);
+ });
+ }
+
+ private void bptChanged(TraceBreakpoint bpt) {
+ if (!(bpt instanceof TraceObjectBreakpointLocation objBpt)) {
+ return;
+ }
+
+ TraceObject obj = objBpt.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
+ .forEach(v -> {
+ long snapMin = v.getMinSnap();
+ long snapMax = v.getMaxSnap();
+ if (snapMin == snapMax) {
+ p.updateMap(snapMin, TimeType.BPT_CHANGED, bpt.getName(snapMin), true);
+ }
+ else {
+ p.updateMap(snapMin, TimeType.BPT_ADDED, bpt.getName(snapMin), true);
+ p.updateMap(snapMax, TimeType.BPT_REMOVED, bpt.getName(snapMax), true);
+ }
+ });
+ }
+
+ private void bptDeleted(TraceBreakpoint bpt) {
+ if (!(bpt instanceof TraceObjectBreakpointLocation objBpt)) {
+ return;
+ }
+
+ TraceObject obj = objBpt.getObject();
+ obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
+ .forEach(v -> {
+ long snap = v.getMaxSnap();
+ p.updateMap(snap, TimeType.BPT_REMOVED, bpt.getName(snap), true);
+ });
+ }
+
+ private void bookmarkAdded(TraceBookmark bookmark) {
+ long snap = bookmark.getLifespan().lmin();
+ p.updateMap(snap, TimeType.BOOKMARK_ADDED, bookmark.getComment(), true);
+ }
+
+ private void bookmarkChanged(TraceBookmark bookmark) {
+ long snapMin = bookmark.getLifespan().lmin();
+ p.updateMap(snapMin, TimeType.BOOKMARK_CHANGED, bookmark.getComment(), false);
+ }
+
+ private void bookmarkDeleted(TraceBookmark bookmark) {
+ long snap = bookmark.getLifespan().lmax();
+ p.updateMap(snap, TimeType.BOOKMARK_REMOVED, bookmark.getComment(), true);
+ }
+
+ private void objectRestored(DomainObjectChangeRecord domainobjectchangerecord1) {
+ p.updateMap();
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeSelectionOverviewColorService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeSelectionOverviewColorService.java
new file mode 100644
index 0000000000..c473e20c25
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeSelectionOverviewColorService.java
@@ -0,0 +1,60 @@
+/* ###
+ * 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.timeoverview.timetype;
+
+import java.util.*;
+
+import ghidra.trace.model.Lifespan;
+
+public class TimeSelectionOverviewColorService
+ extends TimeTypeOverviewColorService {
+
+ @Override
+ public String getName() {
+ return "Trace Selection";
+ }
+
+
+ @Override
+ public void setIndices(TreeSet set) {
+ snapToIndex = new HashMap<>();
+ indexToSnap = new HashMap<>();
+ if (bounds != null) {
+ set.add(bounds.min());
+ int splits = overviewComponent.getOverviewPixelCount();
+ float span = (float)(bounds.lmax() - bounds.lmin())/splits;
+ for (int i = 0; i < splits; i++) {
+ long snap = (long)(bounds.lmin() + i*span);
+ snapToIndex.put(snap, i);
+ indexToSnap.put(i, snap);
+ }
+ }
+ }
+
+ @Override
+ public Lifespan getBounds() {
+ return bounds;
+ }
+
+ @Override
+ public void setBounds(Lifespan bounds) {
+ this.bounds = bounds;
+ TreeSet minset = new TreeSet<>();
+ minset.add(bounds.min());
+ overviewComponent.setLifeSet(minset);
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeType.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeType.java
new file mode 100644
index 0000000000..87a61b8832
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeType.java
@@ -0,0 +1,71 @@
+/* ###
+ * 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.timeoverview.timetype;
+
+import java.awt.Color;
+
+import generic.theme.GColor;
+
+/**
+ * An enum for the different types that are represented by unique colors by the
+ * {@link TimeTypeOverviewColorService}
+ */
+public enum TimeType {
+ THREAD_ADDED("+T", new GColor("color.debugger.plugin.timeoverview.box.type.thread.added")),
+ THREAD_REMOVED("-T", new GColor("color.debugger.plugin.timeoverview.box.type.thread.removed")),
+ THREAD_CHANGED("*T", new GColor("color.debugger.plugin.timeoverview.box.type.thread.changed")),
+ MODULE_ADDED("+M", new GColor("color.debugger.plugin.timeoverview.box.type.module.added")),
+ MODULE_REMOVED("-M", new GColor("color.debugger.plugin.timeoverview.box.type.module.removed")),
+ MODULE_CHANGED("*M", new GColor("color.debugger.plugin.timeoverview.box.type.module.changed")),
+ REGION_ADDED("+R", new GColor("color.debugger.plugin.timeoverview.box.type.region.added")),
+ REGION_REMOVED("-R", new GColor("color.debugger.plugin.timeoverview.box.type.region.removed")),
+ REGION_CHANGED("*R", new GColor("color.debugger.plugin.timeoverview.box.type.region.changed")),
+ BPT_ADDED("+B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.added")),
+ BPT_REMOVED("-B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.removed")),
+ BPT_CHANGED("*B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.changed")),
+ BPT_HIT(">B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.hit")),
+ BOOKMARK_ADDED("+MK", new GColor("color.debugger.plugin.timeoverview.box.type.bookmark.added")),
+ BOOKMARK_REMOVED("-MK", new GColor("color.debugger.plugin.timeoverview.box.type.bookmark.removed")),
+ BOOKMARK_CHANGED("*MK", new GColor("color.debugger.plugin.timeoverview.box.type.bookmark.changed")),
+ UNDEFINED("", new GColor("color.debugger.plugin.timeoverview.box.type.undefined"));
+
+ final private String description;
+ final private Color color;
+
+ TimeType(String description, Color color) {
+ this.description = description;
+ this.color = color;
+ }
+
+ /**
+ * Returns a description of this enum value.
+ *
+ * @return a description of this enum value.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Returns a color of this enum value.
+ *
+ * @return a color of this enum value.
+ */
+ public Color getDefaultColor() {
+ return color;
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewColorService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewColorService.java
new file mode 100644
index 0000000000..7b83164e9e
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewColorService.java
@@ -0,0 +1,209 @@
+/* ###
+ * 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.timeoverview.timetype;
+
+import java.awt.Color;
+import java.math.BigInteger;
+import java.util.*;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+import docking.DialogComponentProvider;
+import docking.action.DockingActionIf;
+import docking.action.builder.ActionBuilder;
+import generic.theme.GThemeDefaults.Colors;
+import ghidra.app.plugin.core.debug.gui.timeoverview.*;
+import ghidra.app.plugin.core.overview.OverviewColorLegendDialog;
+import ghidra.app.plugin.core.overview.OverviewColorPlugin;
+import ghidra.framework.options.ToolOptions;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.trace.model.Lifespan;
+import ghidra.trace.model.Trace;
+import ghidra.util.*;
+
+public class TimeTypeOverviewColorService implements TimeOverviewColorService {
+ private static final String OPTIONS_NAME = "Time Overview";
+ private static final Color DEFAULT_UNDEFINED_COLOR = Color.LIGHT_GRAY;
+ private static final Color DEFAULT_UNINITIALIZED_COLOR = Color.GRAY;
+
+ Map colorMap = new HashMap<>();
+ Color undefinedColor = DEFAULT_UNDEFINED_COLOR;
+ Color uninitializedColor = DEFAULT_UNINITIALIZED_COLOR;
+
+ private Trace trace;
+ protected TimeOverviewColorComponent overviewComponent;
+ private PluginTool tool;
+ private DialogComponentProvider legendDialog;
+ private TimeTypeOverviewLegendPanel legendPanel;
+ private TimeOverviewColorPlugin plugin;
+
+ protected Map indexToSnap = new HashMap<>();
+ protected Map snapToIndex = new HashMap<>();
+ protected Lifespan bounds;
+
+ @Override
+ public String getName() {
+ return "Trace Overview";
+ }
+
+ @Override
+ public HelpLocation getHelpLocation() {
+ return new HelpLocation("DebuggerTimeOverviewPlugin", "plugin");
+ }
+
+ @Override
+ public Color getColor(Long snap) {
+ Set> types = plugin.getTypes(snap);
+ Color c = Colors.BACKGROUND;
+ for (Pair pair : types) {
+ c = ColorUtils.addColors(c, pair.getLeft().getDefaultColor());
+ }
+ return c;
+ }
+
+ @Override
+ public String getToolTipText(Long snap) {
+ // TODO: Right now, there's an inconsistency in how time is rendered
+ // the Time Table uses decimal; the Model Tree, Memview, and Overview
+ // use hex
+ if (snap == null) {
+ return "";
+ }
+ Set> types = plugin.getTypes(snap);
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("");
+ buffer.append(HTMLUtilities.escapeHTML(getName()));
+ buffer.append(" (");
+ buffer.append(Long.toHexString(snap));
+ buffer.append(")");
+ buffer.append("\n");
+ for (Pair pair : types) {
+ TimeType tt = pair.getLeft();
+ String key = pair.getRight();
+ buffer.append(tt.getDescription() + " : " + key + "\n");
+ }
+ return HTMLUtilities.toWrappedHTML(buffer.toString(), 0);
+ }
+
+ @Override
+ public List getActions() {
+ List actions = new ArrayList<>();
+ actions.add(new ActionBuilder("Show Legend", getName())
+ .popupMenuPath("Show Legend")
+ .description("Show types and associated colors")
+ .helpLocation(getHelpLocation())
+ .enabledWhen(c -> c.getContextObject() == overviewComponent)
+ .onAction(c -> tool.showDialog(getLegendDialog()))
+ .build());
+
+ return actions;
+ }
+
+ @Override
+ public void setTrace(Trace trace) {
+ this.trace = trace;
+ }
+
+ @Override
+ public void initialize(PluginTool pluginTool) {
+ this.tool = pluginTool;
+ }
+
+ @Override
+ public void setOverviewComponent(TimeOverviewColorComponent component) {
+ this.overviewComponent = component;
+ }
+
+ /**
+ * Returns the color associated with the given {@link TimeType}
+ *
+ * @param timeType the span type for which to get a color.
+ * @return the color associated with the given {@link TimeType}
+ */
+ public Color getColor(TimeType timeType) {
+ Color color = colorMap.get(timeType);
+ if (color == null) {
+ colorMap.put(timeType, timeType.getDefaultColor());
+ }
+ return color;
+ }
+
+ /**
+ * Sets the color to be associated with a given {@link TimeType}
+ *
+ * @param type the LifespanType for which to assign the color.
+ * @param newColor the new color for the given {@link TimeType}
+ */
+ public void setColor(TimeType type, Color newColor) {
+ ToolOptions options = tool.getOptions(OPTIONS_NAME);
+ options.setColor(type.getDescription(), newColor);
+ }
+
+ private DialogComponentProvider getLegendDialog() {
+ if (legendDialog == null) {
+ legendPanel = new TimeTypeOverviewLegendPanel(this);
+
+ legendDialog =
+ new OverviewColorLegendDialog("Overview Legend", legendPanel, getHelpLocation());
+ }
+ return legendDialog;
+ }
+
+ @Override
+ public Trace getTrace() {
+ return trace;
+ }
+
+ @Override
+ public void setPlugin(TimeOverviewColorPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Long getSnap(int pixelIndex) {
+ BigInteger bigHeight = BigInteger.valueOf(overviewComponent.getOverviewPixelCount());
+ BigInteger bigPixelIndex = BigInteger.valueOf(pixelIndex);
+
+ BigInteger span = BigInteger.valueOf(indexToSnap.size());
+ BigInteger offset = span.multiply(bigPixelIndex).divide(bigHeight);
+ return indexToSnap.get(offset.intValue());
+ }
+
+ @Override
+ public void setIndices(TreeSet set) {
+ snapToIndex = new HashMap<>();
+ indexToSnap = new HashMap<>();
+ int index = 0;
+ Iterator iterator = set.iterator();
+ while (iterator.hasNext()) {
+ Long snap = iterator.next();
+ snapToIndex.put(snap, index);
+ indexToSnap.put(index, snap);
+ index++;
+ }
+ }
+
+ @Override
+ public Lifespan getBounds() {
+ return bounds;
+ }
+
+ @Override
+ public void setBounds(Lifespan bounds) {
+ this.bounds = bounds;
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewLegendPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewLegendPanel.java
new file mode 100644
index 0000000000..27bbb50913
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewLegendPanel.java
@@ -0,0 +1,82 @@
+/* ###
+ * 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.timeoverview.timetype;
+
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.*;
+
+import docking.widgets.label.GLabel;
+import ghidra.util.layout.PairLayout;
+
+/**
+ * A component for displaying the color legend for the {@link TimeTypeOverviewColorService}
+ */
+public class TimeTypeOverviewLegendPanel extends JPanel {
+ private static Dimension COLOR_SIZE = new Dimension(15, 15);
+ private TimeTypeOverviewColorService colorService;
+
+ public TimeTypeOverviewLegendPanel(TimeTypeOverviewColorService colorService) {
+ this.colorService = colorService;
+ setLayout(new PairLayout(4, 10));
+ setBorder(BorderFactory.createEmptyBorder(4, 20, 4, 30));
+ buildLegend();
+ }
+
+ /**
+ * Kick to repaint when the colors have changed.
+ */
+ public void updateColors() {
+ repaint();
+ }
+
+ private void buildLegend() {
+ removeAll();
+ TimeType[] values = TimeType.values();
+ for (TimeType timeType : values) {
+ JPanel panel = new ColorPanel(timeType);
+ add(panel);
+ add(new GLabel(timeType.getDescription()));
+ }
+ }
+
+ private class ColorPanel extends JPanel {
+ private TimeType type;
+
+ ColorPanel(TimeType type) {
+ this.type = type;
+ setPreferredSize(COLOR_SIZE);
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ Color newColor =
+ JColorChooser.showDialog(ColorPanel.this, "Select Color", getBackground());
+ colorService.setColor(type, newColor);
+ }
+ });
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ setBackground(colorService.getColor(type));
+ super.paintComponent(g);
+ }
+
+ }
+
+}