fixed bug where listing would jump when opening/closing large structures

or arrays
This commit is contained in:
ghidravore 2019-08-29 15:13:18 -04:00
parent df971ee613
commit 7b7e730844
12 changed files with 175 additions and 23 deletions

View file

@ -21,10 +21,12 @@ import java.math.BigInteger;
import docking.widgets.fieldpanel.Layout; import docking.widgets.fieldpanel.Layout;
import docking.widgets.fieldpanel.LayoutModel; import docking.widgets.fieldpanel.LayoutModel;
import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener; import docking.widgets.fieldpanel.listener.LayoutModelListener;
import docking.widgets.fieldpanel.support.*; import docking.widgets.fieldpanel.support.*;
import ghidra.app.util.viewer.field.*; import ghidra.app.util.viewer.field.*;
import ghidra.app.util.viewer.format.FormatManager; import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.util.AddressBasedIndexMapper;
import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.Array; import ghidra.program.model.data.Array;
@ -190,7 +192,7 @@ public class ListingModelAdapter implements LayoutModel, ListingModelListener {
public void modelSizeChanged() { public void modelSizeChanged() {
preferredViewSize = null; preferredViewSize = null;
for (LayoutModelListener listener : listeners) { for (LayoutModelListener listener : listeners) {
listener.modelSizeChanged(); listener.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
} }
} }
@ -446,11 +448,16 @@ public class ListingModelAdapter implements LayoutModel, ListingModelListener {
} }
protected void resetIndexMap() { protected void resetIndexMap() {
AddressIndexMap previous = addressToIndexMap.clone();
BigInteger indexCount = addressToIndexMap.getIndexCount(); BigInteger indexCount = addressToIndexMap.getIndexCount();
addressToIndexMap.reset(); addressToIndexMap.reset();
removeUnviewableAddressRanges(); removeUnviewableAddressRanges();
if (!addressToIndexMap.getIndexCount().equals(indexCount)) { if (!addressToIndexMap.getIndexCount().equals(indexCount)) {
modelSizeChanged(); AddressBasedIndexMapper mapper =
new AddressBasedIndexMapper(previous, addressToIndexMap);
for (LayoutModelListener listener : listeners) {
listener.modelSizeChanged(mapper);
}
} }
} }

View file

@ -79,7 +79,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
private LayoutModelListener layoutModelListener = new LayoutModelListener() { private LayoutModelListener layoutModelListener = new LayoutModelListener() {
@Override @Override
public void modelSizeChanged() { public void modelSizeChanged(IndexMapper mapper) {
updateProviders(); updateProviders();
} }

View file

@ -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);
}
}

View file

@ -81,6 +81,19 @@ public class AddressIndexMap {
buildMapping(); 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 * Returns the total number of addresses
* @return the number of addresses in the view * @return the number of addresses in the view

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,8 +15,6 @@
*/ */
package ghidra.app.plugin.core.byteviewer; package ghidra.app.plugin.core.byteviewer;
import ghidra.app.plugin.core.format.DataFormatModel;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.math.BigInteger; import java.math.BigInteger;
@ -28,8 +25,10 @@ import docking.widgets.fieldpanel.Layout;
import docking.widgets.fieldpanel.LayoutModel; import docking.widgets.fieldpanel.LayoutModel;
import docking.widgets.fieldpanel.field.EmptyTextField; import docking.widgets.fieldpanel.field.EmptyTextField;
import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener; import docking.widgets.fieldpanel.listener.LayoutModelListener;
import docking.widgets.fieldpanel.support.SingleRowLayout; import docking.widgets.fieldpanel.support.SingleRowLayout;
import ghidra.app.plugin.core.format.DataFormatModel;
/** /**
* Implements the LayoutModel for ByteViewer Components. * Implements the LayoutModel for ByteViewer Components.
@ -88,7 +87,7 @@ class ByteViewerLayoutModel implements LayoutModel {
public void indexSetChanged() { public void indexSetChanged() {
for (LayoutModelListener listener : listeners) { 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. * Returns the total number of valid indexes.
*/ */
@Override
public BigInteger getNumIndexes() { public BigInteger getNumIndexes() {
return numIndexes; return numIndexes;
} }
@ -127,8 +127,8 @@ class ByteViewerLayoutModel implements LayoutModel {
return null; return null;
} }
List<Field> fields = new ArrayList<Field>(8); List<Field> fields = new ArrayList<Field>(8);
for (int i = 0; i < factorys.length; i++) { for (FieldFactory factory : factorys) {
Field field = factorys[i].getField(index); Field field = factory.getField(index);
if (field != null) { if (field != null) {
fields.add(field); fields.add(field);
} }
@ -137,8 +137,8 @@ class ByteViewerLayoutModel implements LayoutModel {
if (factorys.length > 0) { if (factorys.length > 0) {
FontMetrics fm = factorys[0].getMetrics(); FontMetrics fm = factorys[0].getMetrics();
int height = fm.getMaxAscent() + fm.getMaxDescent(); int height = fm.getMaxAscent() + fm.getMaxDescent();
fields.add(new EmptyTextField(height, factorys[0].getStartX(), 0, fields.add(
factorys[0].getWidth())); new EmptyTextField(height, factorys[0].getStartX(), 0, factorys[0].getWidth()));
} }
else { else {
fields.add(new EmptyTextField(20, 0, 0, 10)); fields.add(new EmptyTextField(20, 0, 0, 10));

View file

@ -30,6 +30,7 @@ import docking.help.HelpService;
import docking.widgets.fieldpanel.*; import docking.widgets.fieldpanel.*;
import docking.widgets.fieldpanel.field.EmptyTextField; import docking.widgets.fieldpanel.field.EmptyTextField;
import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener; import docking.widgets.fieldpanel.listener.LayoutModelListener;
import docking.widgets.fieldpanel.support.SingleRowLayout; import docking.widgets.fieldpanel.support.SingleRowLayout;
import docking.widgets.fieldpanel.support.ViewerPosition; import docking.widgets.fieldpanel.support.ViewerPosition;
@ -759,7 +760,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
indexPanelWidth = getIndexPanelWidth(blocks); indexPanelWidth = getIndexPanelWidth(blocks);
} }
indexFactory.setIndexMap(indexMap, indexPanelWidth); indexFactory.setIndexMap(indexMap, indexPanelWidth);
indexPanel.modelSizeChanged(); indexPanel.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
} }
/** /**
@ -968,7 +969,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
void indexSetChanged() { void indexSetChanged() {
for (LayoutModelListener listener : layoutListeners) { for (LayoutModelListener listener : layoutListeners) {
listener.modelSizeChanged(); listener.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
} }
} }

View file

@ -30,6 +30,7 @@ import docking.widgets.SearchLocation;
import docking.widgets.fieldpanel.Layout; import docking.widgets.fieldpanel.Layout;
import docking.widgets.fieldpanel.LayoutModel; import docking.widgets.fieldpanel.LayoutModel;
import docking.widgets.fieldpanel.field.*; import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener; import docking.widgets.fieldpanel.listener.LayoutModelListener;
import docking.widgets.fieldpanel.support.*; import docking.widgets.fieldpanel.support.*;
import ghidra.app.decompiler.*; import ghidra.app.decompiler.*;
@ -121,15 +122,15 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
} }
@Override @Override
public void modelSizeChanged() { public void modelSizeChanged(IndexMapper mapper) {
for (int i = 0; i < listeners.size(); ++i) { for (int i = 0; i < listeners.size(); ++i) {
listeners.get(i).modelSizeChanged(); listeners.get(i).modelSizeChanged(mapper);
} }
} }
public void modelChanged() { public void modelChanged() {
for (int i = 0; i < listeners.size(); ++i) { for (int i = 0; i < listeners.size(); ++i) {
listeners.get(i).modelSizeChanged(); listeners.get(i).modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
} }
} }

View file

@ -961,23 +961,45 @@ public class FieldPanel extends JPanel
@Override @Override
// BigLayoutModelListener // BigLayoutModelListener
public void modelSizeChanged() { public void modelSizeChanged(IndexMapper indexMapper) {
BigInteger anchorIndex = layouts.isEmpty() ? BigInteger.ZERO : layouts.get(0).getIndex(); BigInteger anchorIndex =
layouts.isEmpty() ? BigInteger.ZERO : indexMapper.map(layouts.get(0).getIndex());
int anchorOffset = layouts.isEmpty() ? 0 : layouts.get(0).getYPos(); int anchorOffset = layouts.isEmpty() ? 0 : layouts.get(0).getYPos();
Point cursorPoint = getCursorPoint(); Point cursorPoint = getCursorPoint();
AnchoredLayout layout = findLayoutOnScreen(cursorPosition.getIndex()); BigInteger cursorIndex = indexMapper.map(cursorPosition.getIndex());
AnchoredLayout layout = findLayoutOnScreen(cursorIndex);
if (layout != null) { if (layout != null) {
anchorIndex = cursorPosition.getIndex(); anchorIndex = cursorIndex;
anchorOffset = layout.getYPos(); anchorOffset = layout.getYPos();
} }
notifyScrollListenerModelChanged(); notifyScrollListenerModelChanged();
layouts = layoutHandler.positionLayoutsAroundAnchor(anchorIndex, anchorOffset); layouts = layoutHandler.positionLayoutsAroundAnchor(anchorIndex, anchorOffset);
updateHighlight(indexMapper);
cursorHandler.updateCursor(cursorPoint); cursorHandler.updateCursor(cursorPoint);
notifyScrollListenerViewChangedAndRepaint(); notifyScrollListenerViewChangedAndRepaint();
invalidate(); 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 @Override
protected void paintComponent(Graphics g) { protected void paintComponent(Graphics g) {
model.flushChanges(); model.flushChanges();

View file

@ -24,6 +24,7 @@ import javax.swing.JFrame;
import docking.widgets.fieldpanel.*; import docking.widgets.fieldpanel.*;
import docking.widgets.fieldpanel.field.*; import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener; import docking.widgets.fieldpanel.listener.LayoutModelListener;
import docking.widgets.fieldpanel.support.*; import docking.widgets.fieldpanel.support.*;
import docking.widgets.indexedscrollpane.IndexedScrollPane; import docking.widgets.indexedscrollpane.IndexedScrollPane;
@ -53,7 +54,7 @@ public class TestBigLayoutModel implements LayoutModel {
public void setNumIndexes(BigInteger n) { public void setNumIndexes(BigInteger n) {
this.numIndexes = n; this.numIndexes = n;
for (LayoutModelListener listener : listeners) { for (LayoutModelListener listener : listeners) {
listener.modelSizeChanged(); listener.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
} }
} }

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * 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. * Called when the data at an index or range of indexes changes.