diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingModelAdapter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingModelAdapter.java index 1e6d6180e2..19b3e4115d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingModelAdapter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingModelAdapter.java @@ -21,10 +21,12 @@ import java.math.BigInteger; import docking.widgets.fieldpanel.Layout; import docking.widgets.fieldpanel.LayoutModel; import docking.widgets.fieldpanel.field.Field; +import docking.widgets.fieldpanel.listener.IndexMapper; import docking.widgets.fieldpanel.listener.LayoutModelListener; import docking.widgets.fieldpanel.support.*; import ghidra.app.util.viewer.field.*; import ghidra.app.util.viewer.format.FormatManager; +import ghidra.app.util.viewer.util.AddressBasedIndexMapper; import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.program.model.address.*; import ghidra.program.model.data.Array; @@ -190,7 +192,7 @@ public class ListingModelAdapter implements LayoutModel, ListingModelListener { public void modelSizeChanged() { preferredViewSize = null; for (LayoutModelListener listener : listeners) { - listener.modelSizeChanged(); + listener.modelSizeChanged(IndexMapper.IDENTITY_MAPPER); } } @@ -446,11 +448,16 @@ public class ListingModelAdapter implements LayoutModel, ListingModelListener { } protected void resetIndexMap() { + AddressIndexMap previous = addressToIndexMap.clone(); BigInteger indexCount = addressToIndexMap.getIndexCount(); addressToIndexMap.reset(); removeUnviewableAddressRanges(); if (!addressToIndexMap.getIndexCount().equals(indexCount)) { - modelSizeChanged(); + AddressBasedIndexMapper mapper = + new AddressBasedIndexMapper(previous, addressToIndexMap); + for (LayoutModelListener listener : listeners) { + listener.modelSizeChanged(mapper); + } } } 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 fee2ae523a..489da3c955 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 @@ -79,7 +79,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc private LayoutModelListener layoutModelListener = new LayoutModelListener() { @Override - public void modelSizeChanged() { + public void modelSizeChanged(IndexMapper mapper) { updateProviders(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressBasedIndexMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressBasedIndexMapper.java new file mode 100644 index 0000000000..dfd340985e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressBasedIndexMapper.java @@ -0,0 +1,44 @@ +/* ### + * 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.util; + +import java.math.BigInteger; + +import docking.widgets.fieldpanel.listener.IndexMapper; +import ghidra.program.model.address.Address; + +/** Implementation of IndexMapper that uses an old and new AddressIndexMap to map indexes + * when the AddressIndexMap changes. + */ +public class AddressBasedIndexMapper implements IndexMapper { + + private AddressIndexMap from; + private AddressIndexMap to; + + public AddressBasedIndexMapper(AddressIndexMap from, AddressIndexMap to) { + this.from = from; + this.to = to; + } + + @Override + public BigInteger map(BigInteger value) { + Address address = from.getAddress(value); + if (address == null) { + return null; + } + return to.getIndex(address); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressIndexMap.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressIndexMap.java index 360fe7b142..55ed05e48b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressIndexMap.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressIndexMap.java @@ -81,6 +81,19 @@ public class AddressIndexMap { buildMapping(); } + private AddressIndexMap(AddressIndexMap source) { + this.numAddresses = source.numAddresses; + indexList = source.indexList; + addressList = source.addressList; + currentViewAddressSet = source.currentViewAddressSet; + originalAddressSet = source.getOriginalAddressSet(); + } + + @Override + public AddressIndexMap clone() { + return new AddressIndexMap(this); + } + /** * Returns the total number of addresses * @return the number of addresses in the view diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerLayoutModel.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerLayoutModel.java index 498fe94694..0ddb5e8dd0 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerLayoutModel.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerLayoutModel.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +15,6 @@ */ package ghidra.app.plugin.core.byteviewer; -import ghidra.app.plugin.core.format.DataFormatModel; - import java.awt.Dimension; import java.awt.FontMetrics; import java.math.BigInteger; @@ -28,8 +25,10 @@ import docking.widgets.fieldpanel.Layout; import docking.widgets.fieldpanel.LayoutModel; import docking.widgets.fieldpanel.field.EmptyTextField; import docking.widgets.fieldpanel.field.Field; +import docking.widgets.fieldpanel.listener.IndexMapper; import docking.widgets.fieldpanel.listener.LayoutModelListener; import docking.widgets.fieldpanel.support.SingleRowLayout; +import ghidra.app.plugin.core.format.DataFormatModel; /** * Implements the LayoutModel for ByteViewer Components. @@ -88,7 +87,7 @@ class ByteViewerLayoutModel implements LayoutModel { public void indexSetChanged() { for (LayoutModelListener listener : listeners) { - listener.modelSizeChanged(); + listener.modelSizeChanged(IndexMapper.IDENTITY_MAPPER); } } @@ -117,6 +116,7 @@ class ByteViewerLayoutModel implements LayoutModel { /** * Returns the total number of valid indexes. */ + @Override public BigInteger getNumIndexes() { return numIndexes; } @@ -127,8 +127,8 @@ class ByteViewerLayoutModel implements LayoutModel { return null; } List fields = new ArrayList(8); - for (int i = 0; i < factorys.length; i++) { - Field field = factorys[i].getField(index); + for (FieldFactory factory : factorys) { + Field field = factory.getField(index); if (field != null) { fields.add(field); } @@ -137,8 +137,8 @@ class ByteViewerLayoutModel implements LayoutModel { if (factorys.length > 0) { FontMetrics fm = factorys[0].getMetrics(); int height = fm.getMaxAscent() + fm.getMaxDescent(); - fields.add(new EmptyTextField(height, factorys[0].getStartX(), 0, - factorys[0].getWidth())); + fields.add( + new EmptyTextField(height, factorys[0].getStartX(), 0, factorys[0].getWidth())); } else { fields.add(new EmptyTextField(20, 0, 0, 10)); 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 72601d1d28..ec117f3885 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 @@ -30,6 +30,7 @@ import docking.help.HelpService; import docking.widgets.fieldpanel.*; import docking.widgets.fieldpanel.field.EmptyTextField; import docking.widgets.fieldpanel.field.Field; +import docking.widgets.fieldpanel.listener.IndexMapper; import docking.widgets.fieldpanel.listener.LayoutModelListener; import docking.widgets.fieldpanel.support.SingleRowLayout; import docking.widgets.fieldpanel.support.ViewerPosition; @@ -759,7 +760,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, indexPanelWidth = getIndexPanelWidth(blocks); } indexFactory.setIndexMap(indexMap, indexPanelWidth); - indexPanel.modelSizeChanged(); + indexPanel.modelSizeChanged(IndexMapper.IDENTITY_MAPPER); } /** @@ -968,7 +969,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener, void indexSetChanged() { for (LayoutModelListener listener : layoutListeners) { - listener.modelSizeChanged(); + listener.modelSizeChanged(IndexMapper.IDENTITY_MAPPER); } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java index e88a9c4570..b21480654b 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java @@ -30,6 +30,7 @@ import docking.widgets.SearchLocation; import docking.widgets.fieldpanel.Layout; import docking.widgets.fieldpanel.LayoutModel; import docking.widgets.fieldpanel.field.*; +import docking.widgets.fieldpanel.listener.IndexMapper; import docking.widgets.fieldpanel.listener.LayoutModelListener; import docking.widgets.fieldpanel.support.*; import ghidra.app.decompiler.*; @@ -121,15 +122,15 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { } @Override - public void modelSizeChanged() { + public void modelSizeChanged(IndexMapper mapper) { for (int i = 0; i < listeners.size(); ++i) { - listeners.get(i).modelSizeChanged(); + listeners.get(i).modelSizeChanged(mapper); } } public void modelChanged() { for (int i = 0; i < listeners.size(); ++i) { - listeners.get(i).modelSizeChanged(); + listeners.get(i).modelSizeChanged(IndexMapper.IDENTITY_MAPPER); } } 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 e2a9f629dd..819dfead1c 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 @@ -961,23 +961,45 @@ public class FieldPanel extends JPanel @Override // BigLayoutModelListener - public void modelSizeChanged() { - BigInteger anchorIndex = layouts.isEmpty() ? BigInteger.ZERO : layouts.get(0).getIndex(); + public void modelSizeChanged(IndexMapper indexMapper) { + BigInteger anchorIndex = + layouts.isEmpty() ? BigInteger.ZERO : indexMapper.map(layouts.get(0).getIndex()); int anchorOffset = layouts.isEmpty() ? 0 : layouts.get(0).getYPos(); Point cursorPoint = getCursorPoint(); - AnchoredLayout layout = findLayoutOnScreen(cursorPosition.getIndex()); + BigInteger cursorIndex = indexMapper.map(cursorPosition.getIndex()); + AnchoredLayout layout = findLayoutOnScreen(cursorIndex); if (layout != null) { - anchorIndex = cursorPosition.getIndex(); + anchorIndex = cursorIndex; anchorOffset = layout.getYPos(); } notifyScrollListenerModelChanged(); layouts = layoutHandler.positionLayoutsAroundAnchor(anchorIndex, anchorOffset); + updateHighlight(indexMapper); cursorHandler.updateCursor(cursorPoint); notifyScrollListenerViewChangedAndRepaint(); invalidate(); } + private void updateHighlight(IndexMapper mapper) { + if (highlight.isEmpty()) { + return; + } + FieldSelection oldHighlight = highlight; + highlight = new FieldSelection(); + for (FieldRange range : oldHighlight) { + FieldLocation start = range.getStart(); + FieldLocation end = range.getEnd(); + BigInteger startIndex = mapper.map(start.getIndex()); + BigInteger endIndex = mapper.map(end.getIndex()); + if (startIndex != null && endIndex != null) { + start.setIndex(startIndex); + end.setIndex(endIndex); + highlight.addRange(start, end); + } + } + } + @Override protected void paintComponent(Graphics g) { model.flushChanges(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/TestBigLayoutModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/TestBigLayoutModel.java index c2803615ea..c2655a40eb 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/TestBigLayoutModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/TestBigLayoutModel.java @@ -24,6 +24,7 @@ import javax.swing.JFrame; import docking.widgets.fieldpanel.*; import docking.widgets.fieldpanel.field.*; +import docking.widgets.fieldpanel.listener.IndexMapper; import docking.widgets.fieldpanel.listener.LayoutModelListener; import docking.widgets.fieldpanel.support.*; import docking.widgets.indexedscrollpane.IndexedScrollPane; @@ -53,7 +54,7 @@ public class TestBigLayoutModel implements LayoutModel { public void setNumIndexes(BigInteger n) { this.numIndexes = n; for (LayoutModelListener listener : listeners) { - listener.modelSizeChanged(); + listener.modelSizeChanged(IndexMapper.IDENTITY_MAPPER); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/IdentityMapper.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/IdentityMapper.java new file mode 100644 index 0000000000..e9249c8bfd --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/IdentityMapper.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 docking.widgets.fieldpanel.listener; + +import java.math.BigInteger; + +/** + * IndexMapper that always maps an index back to itself. + */ +class IdentityMapper implements IndexMapper { + + @Override + public BigInteger map(BigInteger value) { + return value; + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/IndexMapper.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/IndexMapper.java new file mode 100644 index 0000000000..36946ca6ab --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/IndexMapper.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package docking.widgets.fieldpanel.listener; + +import java.math.BigInteger; + +import docking.widgets.fieldpanel.FieldPanel; + +/** + * Interface for mapping indexes when the LayoutModel changes. In other words, if the mapping + * of layout indexes to some data model changes and you want the {@link FieldPanel} to continue + * to display the same model data on the screen, the IndexMapper can be used to convert old + * indexes to new indexes. + */ +public interface IndexMapper { + public IndexMapper IDENTITY_MAPPER = new IdentityMapper(); + + public BigInteger map(BigInteger value); +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/LayoutModelListener.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/LayoutModelListener.java index 327aefdf30..c8f4834468 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/LayoutModelListener.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/LayoutModelListener.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +21,10 @@ public interface LayoutModelListener { /** * Called whenever the number of indexes changed + * @param indexMapper Maps indexes from before the model size change to indexes after + * the model size changed. */ - void modelSizeChanged(); + void modelSizeChanged(IndexMapper indexMapper); /** * Called when the data at an index or range of indexes changes.