diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java index c63788c2a7..b274c5d4ad 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java @@ -937,8 +937,8 @@ public class CodeBrowserPlugin extends Plugin tableFromSelectionAction.setMenuBarData(new MenuData( new String[] { ToolConstants.MENU_SELECTION, "Create Table From Selection" }, null, "SelectUtils")); - tableFromSelectionAction.setHelpLocation( - new HelpLocation("CodeBrowserPlugin", "Selection_Table")); + tableFromSelectionAction + .setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Selection_Table")); } private GhidraProgramTableModel
createTableModel(CodeUnitIterator iterator, @@ -1013,8 +1013,8 @@ public class CodeBrowserPlugin extends Plugin public boolean goToField(Address a, String fieldName, int occurrence, int row, int col, boolean scroll) { - boolean result = SystemUtilities.runSwingNow( - () -> doGoToField(a, fieldName, occurrence, row, col, scroll)); + boolean result = SystemUtilities + .runSwingNow(() -> doGoToField(a, fieldName, occurrence, row, col, scroll)); return result; } @@ -1135,6 +1135,16 @@ public class CodeBrowserPlugin extends Plugin return null; } + @Override + public void addListingDisplayListener(ListingDisplayListener listener) { + connectedProvider.addListingDisplayListener(listener); + } + + @Override + public void removeListingDisplayListener(ListingDisplayListener listener) { + connectedProvider.removeListingDisplayListener(listener); + } + public String getCurrentFieldText() { ListingField lf = getCurrentField(); if (lf instanceof ListingTextField) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java index fedb15026b..0ff677a42c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java @@ -889,8 +889,8 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter int index = saveState.getInt("INDEX", 0); int yOffset = saveState.getInt("Y_OFFSET", 0); ViewerPosition vp = new ViewerPosition(index, 0, yOffset); - listingPanel.getFieldPanel().setViewerPosition(vp.getIndex(), vp.getXOffset(), - vp.getYOffset()); + listingPanel.getFieldPanel() + .setViewerPosition(vp.getIndex(), vp.getXOffset(), vp.getYOffset()); if (program != null) { currentLocation = ProgramLocation.getLocation(program, saveState); if (currentLocation != null) { @@ -906,8 +906,8 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter // (its done in an invoke later) Swing.runLater(() -> { newProvider.doSetProgram(program); - newProvider.listingPanel.getFieldPanel().setViewerPosition(vp.getIndex(), - vp.getXOffset(), vp.getYOffset()); + newProvider.listingPanel.getFieldPanel() + .setViewerPosition(vp.getIndex(), vp.getXOffset(), vp.getYOffset()); newProvider.setLocation(currentLocation); }); } @@ -975,8 +975,8 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter public void actionPerformed(ActionContext context) { boolean show = !listingPanel.isHeaderShowing(); listingPanel.showHeader(show); - getToolBarData().setIcon( - show ? LISTING_FORMAT_COLLAPSE_ICON : LISTING_FORMAT_EXPAND_ICON); + getToolBarData() + .setIcon(show ? LISTING_FORMAT_COLLAPSE_ICON : LISTING_FORMAT_EXPAND_ICON); } } @@ -1039,4 +1039,20 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter return list.toArray(new Highlight[list.size()]); } } + + /** + * Add the ListingDisplayListener to the listing panel + * @param listener the listener to add + */ + public void addListingDisplayListener(ListingDisplayListener listener) { + listingPanel.addListingDisplayListener(listener); + } + + /** + * Remove the ListingDisplayListener from the listing panel + * @param listener the listener to remove + */ + public void removeListingDisplayListener(ListingDisplayListener listener) { + listingPanel.removeListingDisplayListener(listener); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.java index ec197826e3..68f5ca0177 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.java @@ -15,6 +15,12 @@ */ package ghidra.app.services; +import javax.swing.JComponent; + +import docking.action.DockingAction; +import docking.widgets.fieldpanel.FieldPanel; +import docking.widgets.fieldpanel.Layout; +import docking.widgets.fieldpanel.field.Field; import ghidra.app.nav.Navigatable; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.util.HighlightProvider; @@ -29,13 +35,6 @@ import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; -import javax.swing.JComponent; - -import docking.action.DockingAction; -import docking.widgets.fieldpanel.FieldPanel; -import docking.widgets.fieldpanel.Layout; -import docking.widgets.fieldpanel.field.Field; - /** * Service provided by a plugin that shows the listing from a Program, i.e., a * Code Viewer. The service allows other plugins to add components and @@ -224,4 +223,16 @@ public interface CodeViewerService { * @return the current program selection. */ public ProgramSelection getCurrentSelection(); + + /** + * Adds a listener to be notified when the set of visible addresses change. + * @param listener the listener to be notified; + */ + public void addListingDisplayListener(ListingDisplayListener listener); + + /** + * Removes listener from being notified when the set of visible addresses change. + * @param listener the listener to be notified; + */ + public void removeListingDisplayListener(ListingDisplayListener listener); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplayListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplayListener.java new file mode 100644 index 0000000000..4bcf58003c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplayListener.java @@ -0,0 +1,30 @@ +/* ### + * 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.util.viewer.listingpanel; + +import ghidra.program.model.address.AddressSetView; + +/** + * Interface for being notified whenever the set of visible addresses change in the listing. + */ +public interface ListingDisplayListener { + /** + * Callback whenever the set of visible addresses change in the listing. + * @param visibleAddresses the current set of visible addresses in the listing. If no + * visible addresses are in the listing view, then an empty AddressSetView will be passed. + */ + void visibleAddressesChanged(AddressSetView visibleAddresses); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java index 489da3c955..04f611593c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java @@ -45,6 +45,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.program.util.*; +import ghidra.util.Msg; import ghidra.util.layout.HorizontalLayout; public class ListingPanel extends JPanel implements FieldMouseListener, FieldLocationListener, @@ -88,6 +89,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc // don't care } }; + private List displayListeners = new ArrayList<>(); /** * Constructs a new ListingPanel using the given FormatManager and ServiceProvider. @@ -461,6 +463,21 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc for (MarginProvider element : marginProviders) { element.setPixelMap(pixmap); } + + for (ListingDisplayListener listener : displayListeners) { + notifyDisplayListener(listener); + } + } + + private void notifyDisplayListener(ListingDisplayListener listener) { + AddressSetView displayAddresses = pixmap.getAddressSet(); + try { + listener.visibleAddressesChanged(displayAddresses); + } + catch (Throwable t) { + Msg.showError(this, fieldPanel, "Error in Display Listener", + "Execption encountered when notifying listeners of change in display", t); + } } /** @@ -843,8 +860,8 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc FieldLocation dropLoc = new FieldLocation(); ListingField field = (ListingField) fieldPanel.getFieldAt(point.x, point.y, dropLoc); if (field != null) { - return field.getFieldFactory().getProgramLocation(dropLoc.getRow(), dropLoc.getCol(), - field); + return field.getFieldFactory() + .getProgramLocation(dropLoc.getRow(), dropLoc.getCol(), field); } return null; } @@ -1091,4 +1108,11 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc layoutModel.dataChanged(true); } + public void addListingDisplayListener(ListingDisplayListener listener) { + displayListeners.add(listener); + } + + public void removeListingDisplayListener(ListingDisplayListener listener) { + displayListeners.remove(listener); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/VerticalPixelAddressMapImpl.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/VerticalPixelAddressMapImpl.java index 0b07f44cda..b5ba6cc5f0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/VerticalPixelAddressMapImpl.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/VerticalPixelAddressMapImpl.java @@ -128,6 +128,10 @@ public class VerticalPixelAddressMapImpl implements VerticalPixelAddressMap { @Override public AddressSetView getAddressSet() { + // If there are no visible layouts (no open data to display or listing component height = 0) + if (layouts.isEmpty()) { + return new AddressSet(); + } if (viewedAddresses == null) { viewedAddresses = map.getOriginalAddressSet().intersectRange(getStartAddress(), getEndAddress()); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/listingpanel/ListingPanelTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/listingpanel/ListingPanelTest.java index d347210ce7..0cdc41b050 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/listingpanel/ListingPanelTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/listingpanel/ListingPanelTest.java @@ -18,6 +18,7 @@ package ghidra.app.util.viewer.listingpanel; import static org.junit.Assert.*; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import org.junit.*; @@ -51,10 +52,6 @@ public class ListingPanelTest extends AbstractGhidraHeadedIntegrationTest { private AddressFactory addrFactory; private AddressSpace space; - public ListingPanelTest() { - super(); - } - @Before public void setUp() throws Exception { env = new TestEnv(); @@ -305,6 +302,32 @@ public class ListingPanelTest extends AbstractGhidraHeadedIntegrationTest { } + @Test + public void testListingDisplayListener() { + showTool(tool); + + AtomicReference addresses = new AtomicReference<>(); + CodeViewerService cvs = tool.getService(CodeViewerService.class); + cvs.addListingDisplayListener(new ListingDisplayListener() { + @Override + public void visibleAddressesChanged(AddressSetView visibleAddresses) { + addresses.set(visibleAddresses); + } + }); + + assertNull(addresses.get()); + cvs.goTo(new ProgramLocation(program, addr(0x1008000)), false); + assertNotNull(addresses.get()); + assertTrue(addresses.get().contains(addr(0x1008000))); + assertFalse(addresses.get().contains(addr(0x1001000))); + + cvs.goTo(new ProgramLocation(program, addr(0x1001000)), false); + assertNotNull(addresses.get()); + assertFalse(addresses.get().contains(addr(0x1008000))); + assertTrue(addresses.get().contains(addr(0x1001000))); + + } + private void resetFormatOptions() { Options fieldOptions = cb.getFormatManager().getFieldOptions(); List names = fieldOptions.getOptionNames();