From b087cb7ec6d8897c795dc962b9fb9fdf909c83ba Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:56:19 -0500 Subject: [PATCH] GP-6207 - Byte Viewer - Fixed the option to toggle highlighting the current line; Fixed dragging behavior --- .../byteviewer/ByteViewerBGColorModel.java | 30 +++++++++------ .../core/byteviewer/ByteViewerComponent.java | 19 +++++++--- .../ByteViewerComponentProvider.java | 38 +++++++++---------- .../core/byteviewer/ByteViewerPanel.java | 33 ++++++++++++---- .../ProgramByteViewerComponentProvider.java | 16 +++++--- .../widgets/fieldpanel/FieldPanel.java | 10 +++++ 6 files changed, 95 insertions(+), 51 deletions(-) diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerBGColorModel.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerBGColorModel.java index 405c39f682..e5e13e8de9 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerBGColorModel.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerBGColorModel.java @@ -17,32 +17,40 @@ package ghidra.app.plugin.core.byteviewer; import java.awt.Color; import java.math.BigInteger; -import java.util.function.Supplier; import docking.widgets.fieldpanel.support.BackgroundColorModel; +import docking.widgets.fieldpanel.support.FieldLocation; /** - * {@link BackgroundColorModel} that changes the color for the currently focused byteviewer row + * {@link BackgroundColorModel} that changes the color for the currently focused byte viewer row */ public class ByteViewerBGColorModel implements BackgroundColorModel { private Color bgColor = ByteViewerComponentProvider.BG_COLOR; - private Supplier cursorLocSupplier; + private ByteViewerPanel panel; /** - * Creates new model. + * Creates a new model. * - * @param cursorLocSupplier provides the index of the byteviewer-global cursor + * @param panel the byte viewer used to synchronize the current line across components */ - public ByteViewerBGColorModel(Supplier cursorLocSupplier) { - this.cursorLocSupplier = cursorLocSupplier; + public ByteViewerBGColorModel(ByteViewerPanel panel) { + this.panel = panel; } @Override public Color getBackgroundColor(BigInteger index) { - BigInteger cursorIndex = cursorLocSupplier.get(); - return cursorIndex.equals(index) - ? ByteViewerComponentProvider.CURRENT_LINE_COLOR - : bgColor; + + if (!panel.isHighlightCurrentLine()) { + return bgColor; + } + + ByteViewerComponent c = panel.getCurrentComponent(); + FieldLocation loc = c.getCursorLocation(); + BigInteger cursorIndex = loc.getIndex(); + if (index.equals(cursorIndex)) { + return ByteViewerComponentProvider.CURRENT_LINE_COLOR; + } + return bgColor; } @Override diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java index d72e29cd64..0a0045a250 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java @@ -60,6 +60,7 @@ public class ByteViewerComponent extends FieldPanel private int charWidth; private IndexMap indexMap; private ProgramByteBlockSet blockSet; + private ByteViewerLayoutModel layoutModel; private boolean doingRefresh; private boolean doingEdit; @@ -71,24 +72,24 @@ public class ByteViewerComponent extends FieldPanel private FieldSelectionListener liveSelectionListener = (selection, trigger) -> { ByteBlockSelection sel = processFieldSelection(selection); - panel.updateLiveSelection(ByteViewerComponent.this, sel); + panel.updateLiveSelection(this, sel); }; private ByteViewerHoverProvider byteViewerHoverProvider; /** * Constructor * - * @param vpanel the byte viewer panel that this component lives in + * @param panel the byte viewer panel that this component lives in * @param layoutModel the layout model for this component * @param model data format model that knows how the data should be displayed * @param bytesPerLine number of bytes displayed in a row */ - protected ByteViewerComponent(ByteViewerPanel vpanel, ByteViewerLayoutModel layoutModel, + protected ByteViewerComponent(ByteViewerPanel panel, ByteViewerLayoutModel layoutModel, DataFormatModel model, int bytesPerLine) { super(layoutModel, "Byte Viewer"); setFieldDescriptionProvider((l, f) -> getFieldDescription(l, f)); - this.panel = vpanel; + this.panel = panel; this.model = model; this.bytesPerLine = bytesPerLine; this.layoutModel = layoutModel; @@ -693,8 +694,9 @@ public class ByteViewerComponent extends FieldPanel }); setBackgroundColor(ByteViewerComponentProvider.BG_COLOR); + // specialized line coloring - setBackgroundColorModel(new ByteViewerBGColorModel(() -> getCursorLocation().getIndex())); + setBackgroundColorModel(new ByteViewerBGColorModel(panel)); Gui.registerFont(this, ByteViewerComponentProvider.DEFAULT_FONT_ID); @@ -707,6 +709,11 @@ public class ByteViewerComponent extends FieldPanel setHoverProvider(byteViewerHoverProvider); } + @Override + public boolean isDragging() { // open access + return super.isDragging(); + } + /** * Enable help for this component; used the model name as part of the help ID. */ @@ -950,7 +957,7 @@ public class ByteViewerComponent extends FieldPanel // WARNING: unusual situation. This method signature is the same between both // AbstractHoverProvider and the HoverService interface. // AbstractHoverProvider calls the scroll() on the service, but when - // both calls end up at the same method, you will get a stackoverflow. + // both calls end up at the same method, you will get a stack overflow. // We implement a do-nothing here that prevents that. } } diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java index 08d73fd584..b8c7bea390 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java @@ -93,8 +93,6 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt static final String OPTION_FONT = "Font"; private static final String DEFAULT_VIEW = "Hex"; - private static final String CURRENT_LINE_COLOR_OPTION_NAME = - GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR_OPTION_NAME; private static final String OPTION_HIGHLIGHT_CURSOR_LINE = GhidraOptions.HIGHLIGHT_CURSOR_LINE_OPTION_NAME; private static final String OPTION_HIGHLIGHT_MIDDLE_MOUSE_NAME = "Middle Mouse Color"; @@ -215,10 +213,10 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt } @Override - public List getPopupActions(Tool tool, ActionContext context) { + public List getPopupActions(Tool t, ActionContext context) { if (context instanceof ByteViewerActionContext bvContext && bvContext.getComponentProvider() == this) { - return bvContext.getActiveColumn().getPopupActions(tool, bvContext); + return bvContext.getActiveColumn().getPopupActions(t, bvContext); } return null; } @@ -248,12 +246,19 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt @Override public void optionsChanged(ToolOptions options, String optionName, Object oldValue, Object newValue) { + if (options.getName().equals(CATEGORY_BROWSER_FIELDS)) { if (optionName.equals(CURSOR_HIGHLIGHT_BUTTON_NAME)) { CURSOR_MOUSE_BUTTON_NAMES mouseButton = (CURSOR_MOUSE_BUTTON_NAMES) newValue; panel.setHighlightButton(mouseButton.getMouseEventID()); } } + else if (options.getName().equals("ByteViewer")) { + if (optionName.equals(OPTION_HIGHLIGHT_CURSOR_LINE)) { + panel.setHighlightCurrentLineEnabled((Boolean) newValue); + } + } + } // Options.getStringEnum() is deprecated @@ -285,16 +290,15 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt CURSOR_COLOR_UNFOCUSED_EDIT.getId(), help, "Color of the cursor in the unfocused view when editing."); - opt.registerThemeColorBinding(CURRENT_LINE_COLOR_OPTION_NAME, - GhidraOptions.DEFAULT_CURSOR_LINE_COLOR.getId(), help, - "Color of the line containing the cursor."); - opt.registerThemeColorBinding(OPTION_HIGHLIGHT_MIDDLE_MOUSE_NAME, HIGHLIGHT_MIDDLE_MOUSE_COLOR.getId(), help, "The middle-mouse highlight color."); opt.registerThemeFontBinding(OPTION_FONT, DEFAULT_FONT_ID, help, "Font used in the views."); - opt.registerOption(OPTION_HIGHLIGHT_CURSOR_LINE, true, help, + + boolean highlightCurrentLine = true; + opt.registerOption(OPTION_HIGHLIGHT_CURSOR_LINE, highlightCurrentLine, help, "Toggles highlighting background color of line containing the cursor."); + panel.setHighlightCurrentLineEnabled(highlightCurrentLine); opt.addOptionsChangeListener(this); @@ -332,14 +336,6 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt return panel.getCursorLocation(); } - ByteBlockSelection getBlockSelection() { - return panel.getViewerSelection(); - } - - void setBlockSelection(ByteBlockSelection selection) { - panel.setViewerSelection(selection); - } - ByteBlockSet getByteBlockSet() { return blockSet; } @@ -375,7 +371,6 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt panel.repaint(); } - changed |= addNewViews(selectedViews); if (changed) { @@ -513,8 +508,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt model.setByteViewerConfigOptions(configOptions); String viewName = model.getName(); - ByteViewerComponent bvc = - panel.addView(viewName, model, editModeAction.isSelected(), updateViewPosition); + ByteViewerComponent bvc = panel.addView(viewName, model, updateViewPosition); viewMap.put(viewName, bvc); if (configChanged) { tool.setConfigChanged(true); @@ -528,6 +522,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt if (bvc == null) { return; } + panel.removeView(bvc); if (configChanged) { @@ -541,7 +536,8 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt protected abstract void updateSelection(ByteBlockSelection selection); - protected abstract void updateLiveSelection(ByteBlockSelection selection); + protected abstract void updateLiveSelection(ByteViewerComponent bvc, + ByteBlockSelection selection); void dispose() { tool.removePopupActionProvider(this); diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java index 422df01944..91ec6c8a8b 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java @@ -65,6 +65,9 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen private IndexMap indexMap; // maps indexes to the correct block and offset private int blockOffset; private ByteViewerComponent currentView; + + private boolean highlightCurrentLine; + private int highlightButton; private ListenerSet layoutListeners = new ListenerSet<>(LayoutModelListener.class, false); @@ -117,6 +120,15 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen return highlightButton; } + void setHighlightCurrentLineEnabled(boolean b) { + highlightCurrentLine = b; + repaint(); + } + + boolean isHighlightCurrentLine() { + return highlightCurrentLine; + } + void setHighlightButton(int highlightButton) { this.highlightButton = highlightButton; } @@ -269,11 +281,10 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen * * @param viewName name of the format, e.g., Hex, Ascii, etc. * @param model model that understands the format - * @param editMode true if edit mode is on * @param updateViewPosition true if the view position should be set * @return the new component */ - ByteViewerComponent addView(String viewName, DataFormatModel model, boolean editMode, + ByteViewerComponent addView(String viewName, DataFormatModel model, boolean updateViewPosition) { if (viewList.size() != 0) { @@ -447,8 +458,17 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen } } - void updateLiveSelection(ByteViewerComponent source, ByteBlockSelection selection) { - provider.updateLiveSelection(selection); + void updateLiveSelection(ByteViewerComponent bvc, ByteBlockSelection selection) { + + provider.updateLiveSelection(bvc, selection); + + for (ByteViewerComponent c : viewList) { + if (c == bvc) { + continue; + } + c.setViewerSelection(selection); + } + updateIndexColumnCurrentLine(); } @@ -514,7 +534,7 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen setLayout(new BorderLayout(10, 0)); setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); - + setFont(ByteViewerComponentProvider.DEFAULT_FONT); // side-effect sets fontMetrics // for the index/address column @@ -526,8 +546,7 @@ public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListen indexPanel.setFocusable(false); indexPanel.addLayoutListener(this); indexPanel.setBackgroundColor(ByteViewerComponentProvider.BG_COLOR); - indexPanel.setBackgroundColorModel( - new ByteViewerBGColorModel(() -> getCurrentComponent().getCursorLocation().getIndex())); + indexPanel.setBackgroundColorModel(new ByteViewerBGColorModel(this)); indexedView = new ByteViewerIndexedView(indexPanel); IndexedScrollPane indexedScrollPane = new IndexedScrollPane(indexedView); diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java index 5ac2fd0e80..7cf1f54763 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java @@ -618,13 +618,17 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi } @Override - protected void updateLiveSelection(ByteBlockSelection selection) { - if (blockSet != null) { - AbstractSelectionPluginEvent event = - blockSet.getPluginEvent(plugin.getName(), selection); - liveSelection = event.getSelection(); - updateTitle(); + protected void updateLiveSelection(ByteViewerComponent sourceComponent, + ByteBlockSelection blockSelection) { + + if (blockSet == null) { + return; } + + AbstractSelectionPluginEvent event = + blockSet.getPluginEvent(plugin.getName(), blockSelection); + liveSelection = event.getSelection(); + updateTitle(); } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java index 15ccad9bad..e4c4e4e536 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java @@ -930,6 +930,10 @@ public class FieldPanel extends JPanel return !selectionHandler.isInProgress(); } + protected boolean isDragging() { + return mouseHandler.isDragging(); + } + /** * Sets the selection color * @@ -1939,7 +1943,13 @@ public class FieldPanel extends JPanel else if (!selectionHandler.isInProgress()) { selectionHandler.clearSelection(); } + selectionHandler.endSelectionSequence(); + didDrag = false; + } + + boolean isDragging() { + return didDrag; } /**