Merge remote-tracking branch 'origin/GT-2846-2847-dragonmacher-decompiler-navigation'

This commit is contained in:
ghidra1 2019-05-09 16:57:06 -04:00
commit 44e8f64d7e
17 changed files with 705 additions and 316 deletions

View File

@ -36,6 +36,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Reference;
import ghidra.util.datastruct.LRUMap;
import ghidra.util.task.TaskMonitor;
public class ProgramBigListingModel implements ListingModel, FormatModelListener,
@ -51,6 +52,9 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
private DummyFieldFactory dummyFactory;
private List<ListingModelListener> listeners = new ArrayList<>();
// Use a cache so that simple arrowing to-and-fro with the keyboard will respond quickly
private LRUMap<Address, Layout> layoutCache = new LRUMap<>(10);
public ProgramBigListingModel(Program program, FormatManager formatMgr) {
this.program = program;
this.listing = program.getListing();
@ -87,6 +91,10 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
showNonExternalFunctionPointerFormat = (Boolean) newValue;
formatModelChanged(null);
}
// There are quite a few options that affect the display of the the layouts. Flush
// the cache on any change, as it is simpler than tracking individual options.
layoutCache.clear();
}
@Override
@ -115,6 +123,16 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
@Override
public Layout getLayout(Address addr, boolean isGapAddress) {
Layout layout = layoutCache.get(addr);
if (layout == null) {
layout = doGetLayout(addr, isGapAddress);
layoutCache.put(addr, layout);
}
return layout;
}
public Layout doGetLayout(Address addr, boolean isGapAddress) {
List<RowLayout> list = new ArrayList<>();
FieldFormatModel format;
CodeUnit cu = listing.getCodeUnitAt(addr);
@ -477,12 +495,16 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
}
protected void notifyDataChanged(boolean updateImmediately) {
layoutCache.clear();
for (ListingModelListener listener : listeners) {
listener.dataChanged(updateImmediately);
}
}
private void notifyModelSizeChanged() {
layoutCache.clear();
for (ListingModelListener listener : listeners) {
listener.modelSizeChanged();
}
@ -523,15 +545,14 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
return program.isClosed();
}
/**
* @see ghidra.framework.model.DomainObjectListener#domainObjectChanged(ghidra.framework.model.DomainObjectChangedEvent)
*/
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
if (!program.isClosed()) {
boolean updateImmediately = ev.numRecords() <= 5;
notifyDataChanged(updateImmediately);
if (program.isClosed()) {
return;
}
boolean updateImmediately = ev.numRecords() <= 5;
notifyDataChanged(updateImmediately);
}
@Override

View File

@ -70,7 +70,7 @@ public abstract class AbstractProgramBasedTest extends AbstractGhidraHeadedInteg
* Override this method if you need to build your own program.
*
* @return the program to use for this test.
* @throws Exception if an exceptioun is thrown opening the program
* @throws Exception if an exception is thrown opening the program
*/
protected Program getProgram() throws Exception {
return env.getProgram(getProgramName());

View File

@ -56,10 +56,6 @@ public class OperandFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest
private Options fieldOptions;
private Program program;
public OperandFieldFactoryTest() {
super();
}
@Before
public void setUp() throws Exception {

View File

@ -41,10 +41,6 @@ public class PostCommentFieldFactoryTest extends AbstractGhidraHeadedIntegration
private Options fieldOptions;
private Program program;
public PostCommentFieldFactoryTest() {
super();
}
@Before
public void setUp() throws Exception {

View File

@ -516,14 +516,21 @@
<LI><B>Control-shift-click</B> - Triggers the Listing in a <A href=
"help/topics/Snapshots/Snapshots.html">Snapshots</A> view to navigate to the address
denoted by the symbol that was clicked.</LI>
<LI><B>Middle-mouse</B> - If you press the middle mouse
button the decompiler will highlight every occurrence of a variable or constant
under the current cursor location (the button changed in the tool options under
<B>Browser Field-&gt;Cursor Text Highlight</B>).</LI>
</UL>
<P><IMG alt="" src="../../shared/note.png" border="0">&nbsp; If you press the middle mouse
button (the button changed in the tool options under <B>Browser Field-&gt;Cursor Text
Highlight</B>), the decompiler will highlight every occurrence of a variable or constant
under the current cursor location.<BR>
</P>
</BLOCKQUOTE><BR>
<BLOCKQUOTE>
<P><IMG alt="" src="../../shared/tip.png" border="0">You can navigate to the target
of a <code>goto</code> statement by double-clicking its label (you can also double-click
a brace to navigate to the matching brace).<BR>
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<BR>
<BR>

View File

@ -15,6 +15,8 @@
*/
package ghidra.app.decompiler;
import java.util.Objects;
import ghidra.framework.options.SaveState;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
@ -52,7 +54,7 @@ public class DecompilerLocation extends ProgramLocation {
/**
* Results from the decompilation
*
* @return C-AST, DFG, and CFG object. Can return null if there are no results attached to this location.
* @return C-AST, DFG, and CFG object. null if there are no results attached to this location
*/
public DecompileResults getDecompile() {
return results;
@ -85,29 +87,36 @@ public class DecompilerLocation extends ProgramLocation {
@Override
public boolean equals(Object obj) {
if (this == obj)
if (obj == null) {
return false;
}
if (this == obj) {
return true;
if (!super.equals(obj))
}
if (getClass() != obj.getClass()) {
return false;
if (getClass() != obj.getClass())
}
if (!super.equals(obj)) {
return false;
}
DecompilerLocation other = (DecompilerLocation) obj;
if (charPos != other.charPos)
if (charPos != other.charPos) {
return false;
if (functionEntryPoint == null) {
if (other.functionEntryPoint != null)
return false;
}
else if (!functionEntryPoint.equals(other.functionEntryPoint))
if (lineNumber != other.lineNumber) {
return false;
if (lineNumber != other.lineNumber)
return false;
if (tokenName == null) {
if (other.tokenName != null)
return false;
}
else if (!tokenName.equals(other.tokenName))
if (!Objects.equals(functionEntryPoint, other.functionEntryPoint)) {
return false;
}
if (!Objects.equals(tokenName, other.tokenName)) {
return false;
}
return true;
}
@ -137,4 +146,19 @@ public class DecompilerLocation extends ProgramLocation {
public int getCharPos() {
return charPos;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName());
buf.append('@');
buf.append(addr.toString());
buf.append(", line=");
buf.append(lineNumber);
buf.append(", character=");
buf.append(charPos);
buf.append(", token=");
buf.append(tokenName);
return buf.toString();
}
}

View File

@ -15,22 +15,21 @@
*/
package ghidra.app.decompiler.component;
import ghidra.app.decompiler.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import java.awt.Color;
import java.util.*;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation;
import ghidra.app.decompiler.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* Class to handle highlights for a decompiled function.
*/
public class ClangHighlightController {
public abstract class ClangHighlightController {
// Note: Most of the methods in this class were extracted from the ClangLayoutController class
// and the DecompilerPanel class.
@ -40,14 +39,16 @@ public class ClangHighlightController {
protected Color defaultSpecialColor = new Color(255, 100, 0, 128); // Default color for specially highlighted tokens
protected Color defaultParenColor = new Color(255, 255, 0, 128); // Default color for highlighting parentheses
protected HashSet<ClangToken> highlightTokenSet = new HashSet<ClangToken>();
protected HashSet<ClangToken> highlightTokenSet = new HashSet<>();
protected ArrayList<ClangHighlightListener> highlightListenerList =
new ArrayList<ClangHighlightListener>();
protected ArrayList<ClangHighlightListener> highlightListenerList = new ArrayList<>();
public ClangHighlightController() {
}
public abstract void fieldLocationChanged(FieldLocation location, Field field,
EventTrigger trigger);
void loadOptions(DecompileOptions options) {
Color currentVariableHighlightColor = options.getCurrentVariableHighlightColor();
if (currentVariableHighlightColor != null) {
@ -115,18 +116,21 @@ public class ClangHighlightController {
else if (node instanceof ClangToken) {
ClangToken tok = (ClangToken) node;
Varnode vn = DecompilerUtils.getVarnodeRef(tok);
if (varnodes.contains(vn))
if (varnodes.contains(vn)) {
addHighlight(tok, highlightColor);
}
if (vn == specificvn) { // Look for specific varnode to label with specialColor
if ((specificop != null) && (tok.getPcodeOp() == specificop))
if ((specificop != null) && (tok.getPcodeOp() == specificop)) {
addHighlight(tok, specialColor);
}
}
}
}
notifyListeners();
}
public void addPcodeOpsToHighlight(ClangNode parentNode, Set<PcodeOp> ops, Color highlightColor) {
public void addPcodeOpsToHighlight(ClangNode parentNode, Set<PcodeOp> ops,
Color highlightColor) {
int nchild = parentNode.numChildren();
for (int i = 0; i < nchild; ++i) {
ClangNode node = parentNode.Child(i);
@ -136,8 +140,9 @@ public class ClangHighlightController {
else if (node instanceof ClangToken) {
ClangToken tok = (ClangToken) node;
PcodeOp op = tok.getPcodeOp();
if (ops.contains(op))
if (ops.contains(op)) {
addHighlight(tok, highlightColor);
}
}
}
notifyListeners();
@ -188,24 +193,27 @@ public class ClangHighlightController {
* @return a list of all tokens that were highlighted.
*/
public List<ClangToken> addHighlightParen(ClangSyntaxToken tok, Color highlightColor) {
ArrayList<ClangToken> tokenList = new ArrayList<ClangToken>();
ArrayList<ClangToken> tokenList = new ArrayList<>();
int paren = tok.getOpen();
if (paren == -1)
if (paren == -1) {
paren = tok.getClose();
if (paren == -1)
}
if (paren == -1) {
return tokenList; // Not a parenthesis
}
ClangNode par = tok.Parent();
while (par != null) {
boolean outside = true;
if (par instanceof ClangTokenGroup) {
ArrayList<ClangNode> list = new ArrayList<ClangNode>();
ArrayList<ClangNode> list = new ArrayList<>();
((ClangTokenGroup) par).flatten(list);
for (int i = 0; i < list.size(); ++i) {
ClangToken tk = (ClangToken) list.get(i);
if (tk instanceof ClangSyntaxToken) {
ClangSyntaxToken syn = (ClangSyntaxToken) tk;
if (syn.getOpen() == paren)
if (syn.getOpen() == paren) {
outside = false;
}
else if (syn.getClose() == paren) {
outside = true;
addHighlight(syn, highlightColor);
@ -224,72 +232,20 @@ public class ClangHighlightController {
}
public void addHighlightBrace(ClangSyntaxToken token, Color highlightColor) {
ClangNode parent = token.Parent();
String text = token.getText();
if ("{".equals(text)) {
highlightBrace(token, parent, false, highlightColor);
return;
}
if ("}".equals(text)) {
highlightBrace(token, parent, true, highlightColor);
return;
}
notifyListeners();
}
private void highlightBrace(ClangSyntaxToken startToken, ClangNode parent, boolean forward,
Color highlightColor) {
List<ClangNode> list = new ArrayList<ClangNode>();
parent.flatten(list);
if (!forward) {
Collections.reverse(list);
}
Stack<ClangSyntaxToken> braceStack = new Stack<ClangSyntaxToken>();
for (int i = 0; i < list.size(); ++i) {
ClangToken token = (ClangToken) list.get(i);
if (token instanceof ClangSyntaxToken) {
ClangSyntaxToken syntaxToken = (ClangSyntaxToken) token;
if (startToken == syntaxToken) {
// found our starting token, take the current value on the stack
ClangSyntaxToken matchingBrace = braceStack.pop();
matchingBrace.setMatchingToken(true);
addHighlight(matchingBrace, highlightColor);
return;
}
if (!isBrace(syntaxToken)) {
continue;
}
if (braceStack.isEmpty()) {
braceStack.push(syntaxToken);
continue;
}
ClangSyntaxToken lastToken = braceStack.peek();
if (isMatchingBrace(lastToken, syntaxToken)) {
braceStack.pop();
}
else {
braceStack.push(syntaxToken);
}
}
if (DecompilerUtils.isBrace(token)) {
highlightBrace(token, highlightColor);
notifyListeners();
}
}
private boolean isBrace(ClangSyntaxToken token) {
String text = token.getText();
return "{".equals(text) || "}".equals(text);
}
private void highlightBrace(ClangSyntaxToken startToken, Color highlightColor) {
private boolean isMatchingBrace(ClangSyntaxToken braceToken, ClangSyntaxToken otherBraceToken) {
String brace = braceToken.getText();
String otherBrace = otherBraceToken.getText();
return !brace.equals(otherBrace);
ClangSyntaxToken matchingBrace = DecompilerUtils.getMatchingBrace(startToken);
if (matchingBrace != null) {
matchingBrace.setMatchingToken(true); // this is a signal to the painter
addHighlight(matchingBrace, highlightColor);
}
}
/**
@ -298,13 +254,14 @@ public class ClangHighlightController {
*/
public void addHighlightFill() {
ClangTokenGroup lastgroup = null;
ArrayList<ClangNode> newhi = new ArrayList<ClangNode>();
ArrayList<Color> newcolor = new ArrayList<Color>();
ArrayList<ClangNode> newhi = new ArrayList<>();
ArrayList<Color> newcolor = new ArrayList<>();
for (ClangToken tok : highlightTokenSet) {
if (tok.Parent() instanceof ClangTokenGroup) {
ClangTokenGroup par = (ClangTokenGroup) tok.Parent();
if (par == lastgroup)
if (par == lastgroup) {
continue;
}
lastgroup = par;
int beg = -1;
int end = par.numChildren();
@ -313,8 +270,9 @@ public class ClangHighlightController {
ClangToken token = (ClangToken) par.Child(j);
Color curcolor = token.getHighlight();
if (curcolor != null) {
if (beg == -1)
if (beg == -1) {
beg = j;
}
else {
end = j;
for (int k = beg + 1; k < end; ++k) {
@ -330,45 +288,22 @@ public class ClangHighlightController {
beg = -1;
}
}
else
else {
beg = -1;
}
}
}
}
for (int i = 0; i < newhi.size(); ++i) {
ClangToken tok = (ClangToken) newhi.get(i);
if (tok.getHighlight() != null)
if (tok.getHighlight() != null) {
continue;
}
addHighlight(tok, newcolor.get(i));
}
notifyListeners();
}
public void fieldLocationChanged(FieldLocation location, Field field, EventTrigger trigger) {
// Do nothing.
// clearHighlights();
//
// if (!(field instanceof ClangTextField)) {
// return;
// }
//
// ClangToken tok = ((ClangTextField) field).getToken(location);
// if (tok == null) {
// return;
// }
//
//// // clear any highlighted searchResults
//// decompilerPanel.setSearchResults(null);
//
// addHighlight(tok, defaultHighlightColor);
// if (tok instanceof ClangSyntaxToken) {
// addHighlightParen((ClangSyntaxToken) tok, defaultParenColor);
// addHighlightBrace((ClangSyntaxToken) tok, defaultParenColor);
// }
}
public boolean addListener(ClangHighlightListener listener) {
return highlightListenerList.add(listener);
}

View File

@ -25,6 +25,8 @@ import javax.swing.JComponent;
import javax.swing.JPanel;
import docking.DockingUtils;
import docking.util.AnimationUtils;
import docking.util.SwingAnimationCallback;
import docking.widgets.EventTrigger;
import docking.widgets.SearchLocation;
import docking.widgets.fieldpanel.FieldPanel;
@ -48,9 +50,8 @@ import ghidra.util.*;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
/**
* Class to handle the display of a decompiled function.
* Class to handle the display of a decompiled function
*/
public class DecompilerPanel extends JPanel implements FieldMouseListener, FieldLocationListener,
FieldSelectionListener, ClangHighlightListener {
@ -62,7 +63,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
private final DecompilerController controller;
private final DecompileOptions options;
private FieldPanel codeViewer;
private DecompilerFieldPanel fieldPanel;
private ClangLayoutController layoutMgr;
private HighlightFactory hlFactory;
private ClangHighlightController highlightController;
@ -93,13 +94,13 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
hlFactory = new SearchHighlightFactory();
layoutMgr = new ClangLayoutController(options, this, metrics, hlFactory);
codeViewer = new FieldPanel(layoutMgr);
fieldPanel = new DecompilerFieldPanel(layoutMgr);
setBackground(options.getCodeViewerBackgroundColor());
IndexedScrollPane scroller = new IndexedScrollPane(codeViewer);
codeViewer.addFieldSelectionListener(this);
codeViewer.addFieldMouseListener(this);
codeViewer.addFieldLocationListener(this);
IndexedScrollPane scroller = new IndexedScrollPane(fieldPanel);
fieldPanel.addFieldSelectionListener(this);
fieldPanel.addFieldMouseListener(this);
fieldPanel.addFieldLocationListener(this);
decompilerHoverProvider = new DecompilerHoverProvider();
@ -124,7 +125,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
public FieldPanel getFieldPanel() {
return codeViewer;
return fieldPanel;
}
@Override
@ -133,15 +134,15 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
if (useNonFunctionColor) {
bg = NON_FUNCTION_BACKGROUND_COLOR_DEF;
}
if (codeViewer != null) {
codeViewer.setBackgroundColor(bg);
if (fieldPanel != null) {
fieldPanel.setBackgroundColor(bg);
}
super.setBackground(bg);
}
/**
* This function sets the current window display based
* on our display state.
* This function sets the current window display based on our display state
* @param decompileData the new data
*/
void setDecompileData(DecompileData decompileData) {
if (layoutMgr == null) {
@ -207,7 +208,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return;
}
if (viewerPosition != null) {
codeViewer.setViewerPosition(viewerPosition.getIndex(), viewerPosition.getXOffset(),
fieldPanel.setViewerPosition(viewerPosition.getIndex(), viewerPosition.getXOffset(),
viewerPosition.getYOffset());
}
List<ClangToken> tokens =
@ -215,25 +216,79 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
if (location instanceof DecompilerLocation) {
DecompilerLocation decompilerLocation = (DecompilerLocation) location;
codeViewer.goTo(BigInteger.valueOf(decompilerLocation.getLineNumber()), 0, 0,
fieldPanel.goTo(BigInteger.valueOf(decompilerLocation.getLineNumber()), 0, 0,
decompilerLocation.getCharPos(), false);
}
else if (!tokens.isEmpty()) {
int firstfield = DecompilerUtils.findIndexOfFirstField(tokens, layoutMgr.getFields());
// Put cursor on first token in the list
if (firstfield != -1) {
codeViewer.goTo(BigInteger.valueOf(firstfield), 0, 0, 0, false);
}
goToBeginningOfLine(tokens);
}
}
/**
* Translate Ghidra address to decompiler address.
* Functions within an overlay space are decompiled
* in their physical space, therefore decompiler results
* refer to the functions underlying .physical space
* @param addr
* @return
* Put cursor on first token in the list
* @param tokens the tokens to search for
*/
private void goToBeginningOfLine(List<ClangToken> tokens) {
int firstLineNumber = DecompilerUtils.findIndexOfFirstField(tokens, layoutMgr.getFields());
if (firstLineNumber != -1) {
fieldPanel.goTo(BigInteger.valueOf(firstLineNumber), 0, 0, 0, false);
}
}
private void goToToken(ClangToken token) {
ClangLine line = token.getLineParent();
int offset = 0;
List<ClangToken> tokens = line.getAllTokens();
for (ClangToken lineToken : tokens) {
if (lineToken.equals(token)) {
break;
}
offset += lineToken.getText().length();
}
// -1 since the FieldPanel is 0-based; we are 1-based
int lineNumber = line.getLineNumber() - 1;
int column = offset;
FieldLocation start = getCursorPosition();
int distance = getOffscreenDistance(lineNumber);
if (distance == 0) {
fieldPanel.navigateTo(lineNumber, column);
return;
}
ScrollingCallback callback = new ScrollingCallback(start, lineNumber, column, distance);
AnimationUtils.executeSwingAnimationCallback(callback);
}
private int getOffscreenDistance(int line) {
AnchoredLayout start = fieldPanel.getVisibleStartLayout();
int visibleStartLine = start.getIndex().intValue();
if (visibleStartLine > line) {
// the end is off the top of the screen
return visibleStartLine - line;
}
AnchoredLayout end = fieldPanel.getVisibleEndLayout();
int visibleEndLine = end.getIndex().intValue();
if (visibleEndLine < line) {
// the end is off the bottom of the screen
return line - visibleEndLine;
}
return 0;
}
/**
* Translate Ghidra address to decompiler address. Functions within an overlay space are
* decompiled in their physical space, therefore decompiler results refer to the
* functions underlying .physical space
*
* @param addr the Ghidra address
* @return the decompiler address
*/
private Address translateAddress(Address addr) {
Function func = decompileData.getFunction();
@ -248,12 +303,12 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
/**
* Translate Ghidra address set to decompiler address set.
* Functions within an overlay space are decompiled
* in their physical space, therefore decompiler results
* Translate Ghidra address set to decompiler address set. Functions within an overlay
* space are decompiled in their physical space, therefore decompiler results
* refer to the functions underlying .physical space
* @param set
* @return
*
* @param set the Ghidra addresses
* @return the decompiler addresses
*/
private AddressSetView translateSet(AddressSetView set) {
Function func = decompileData.getFunction();
@ -288,7 +343,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
DecompilerUtils.getTokens(layoutMgr.getRoot(), translateSet(selection));
fieldSelection = DecompilerUtils.getFieldSelection(tokens);
}
codeViewer.setSelection(fieldSelection);
fieldPanel.setSelection(fieldSelection);
}
public void setDecompilerHoverProvider(DecompilerHoverProvider provider) {
@ -363,45 +418,84 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
ClangTextField textField = (ClangTextField) field;
ClangToken token = textField.getToken(location);
if (token instanceof ClangFuncNameToken) {
Function function =
DecompilerUtils.getFunction(controller.getProgram(), (ClangFuncNameToken) token);
if (function != null) {
controller.goToFunction(function, newWindow);
}
else {
String labelName = token.getText();
if (labelName.startsWith("func_0x")) {
try {
Address addr = decompileData.getFunction().getEntryPoint().getAddress(
labelName.substring(7));
controller.goToAddress(addr, newWindow);
}
catch (AddressFormatException e) {
controller.goToLabel(labelName, newWindow);
}
}
}
tryGoToFunction(token, newWindow);
}
else if (token instanceof ClangLabelToken) {
Address addr = token.getMinAddress();
controller.goToAddress(addr, newWindow);
tryGoToLabel((ClangLabelToken) token, newWindow);
}
else if (token instanceof ClangVariableToken) {
tryGoToVarnode((ClangVariableToken) token, newWindow);
}
else if (token instanceof ClangCommentToken) {
// special cases
// -comments: these no longer use tokens for each item, but are one composite field
FieldElement clickedElement = textField.getClickedObject(location);
if (clickedElement instanceof AnnotatedTextFieldElement) {
AnnotatedTextFieldElement annotation = (AnnotatedTextFieldElement) clickedElement;
controller.annotationClicked(annotation, event, newWindow);
tryGoToComment(location, event, textField, token, newWindow);
}
else if (token instanceof ClangSyntaxToken) {
tryGoToSyntaxToken((ClangSyntaxToken) token);
}
}
private void tryGoToComment(FieldLocation location, MouseEvent event, ClangTextField textField,
ClangToken token, boolean newWindow) {
// special cases
// -comments: these no longer use tokens for each item, but are one composite field
FieldElement clickedElement = textField.getClickedObject(location);
if (clickedElement instanceof AnnotatedTextFieldElement) {
AnnotatedTextFieldElement annotation = (AnnotatedTextFieldElement) clickedElement;
controller.annotationClicked(annotation, event, newWindow);
return;
}
String text = clickedElement.getText();
String word = StringUtilities.findWord(text, location.col);
tryGoToScalar(word, newWindow);
}
private void tryGoToFunction(ClangToken token, boolean newWindow) {
Function function =
DecompilerUtils.getFunction(controller.getProgram(), (ClangFuncNameToken) token);
if (function != null) {
controller.goToFunction(function, newWindow);
return;
}
// TODO no idea what this is supposed to be handling...someone doc this please
String labelName = token.getText();
if (labelName.startsWith("func_0x")) {
try {
Address addr =
decompileData.getFunction().getEntryPoint().getAddress(labelName.substring(7));
controller.goToAddress(addr, newWindow);
}
catch (AddressFormatException e) {
controller.goToLabel(labelName, newWindow);
}
}
}
private void tryGoToLabel(ClangLabelToken token, boolean newWindow) {
ClangNode node = token.Parent();
if (node instanceof ClangStatement) {
// check for a goto label
ClangTokenGroup root = layoutMgr.getRoot();
ClangLabelToken destination = DecompilerUtils.getGoToTargetToken(root, token);
if (destination != null) {
goToToken(destination);
return;
}
}
String text = clickedElement.getText();
String word = StringUtilities.findWord(text, location.col);
tryGoToScalar(word, newWindow);
Address addr = token.getMinAddress();
controller.goToAddress(addr, newWindow);
}
private void tryGoToSyntaxToken(ClangSyntaxToken token) {
if (DecompilerUtils.isBrace(token)) {
ClangSyntaxToken otherBrace = DecompilerUtils.getMatchingBrace(token);
if (otherBrace != null) {
goToToken(otherBrace);
}
}
}
@ -490,8 +584,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
if (!decompileData.hasDecompileResults()) {
return null;
}
Field currentField = codeViewer.getCurrentField();
FieldLocation cursorPosition = codeViewer.getCursorLocation();
Field currentField = fieldPanel.getCurrentField();
FieldLocation cursorPosition = fieldPanel.getCursorLocation();
return getProgramLocation(currentField, cursorPosition);
}
@ -514,6 +608,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
return;
}
// only broadcast when the user is clicking around
if (trigger == EventTrigger.GUI_ACTION) {
ProgramLocation programLocation = getProgramLocation(field, location);
if (programLocation != null) {
@ -605,13 +700,13 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
public FieldLocation getCursorPosition() {
return codeViewer.getCursorLocation();
return fieldPanel.getCursorLocation();
}
public void setCursorPosition(FieldLocation fieldLocation) {
codeViewer.setCursorPosition(fieldLocation.getIndex(), fieldLocation.getFieldNum(),
fieldPanel.setCursorPosition(fieldLocation.getIndex(), fieldLocation.getFieldNum(),
fieldLocation.getRow(), fieldLocation.getCol());
codeViewer.scrollToCursor();
fieldPanel.scrollToCursor();
}
/**
@ -619,7 +714,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
* @return a single selected token; null if there is no selection or multiple tokens selected.
*/
public ClangToken getSelectedToken() {
FieldSelection selection = codeViewer.getSelection();
FieldSelection selection = fieldPanel.getSelection();
if (selection.isEmpty()) {
return null;
}
@ -635,8 +730,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
public ClangToken getTokenAtCursor() {
FieldLocation cursorPosition = codeViewer.getCursorLocation();
Field field = codeViewer.getCurrentField();
FieldLocation cursorPosition = fieldPanel.getCursorLocation();
Field field = fieldPanel.getCurrentField();
if (field == null) {
return null;
}
@ -654,10 +749,10 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
public void setHoverMode(boolean enabled) {
decompilerHoverProvider.setHoverEnabled(enabled);
if (enabled) {
codeViewer.setHoverProvider(decompilerHoverProvider);
fieldPanel.setHoverProvider(decompilerHoverProvider);
}
else {
codeViewer.setHoverProvider(null);
fieldPanel.setHoverProvider(null);
}
}
@ -702,49 +797,25 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
}
}
class SearchHighlightFactory implements HighlightFactory {
@Override
public Highlight[] getHighlights(Field field, String text, int cursorTextOffset) {
if (currentSearchLocation == null) {
return new Highlight[0];
}
ClangTextField cField = (ClangTextField) field;
int highlightLine = cField.getLineNumber();
FieldLocation searchCursorLocation =
((FieldBasedSearchLocation) currentSearchLocation).getFieldLocation();
int searchLineNumber = searchCursorLocation.getIndex().intValue() + 1;
if (highlightLine != searchLineNumber) {
// only highlight the match on the actual line
return new Highlight[0];
}
return new Highlight[] { new Highlight(currentSearchLocation.getStartIndexInclusive(),
currentSearchLocation.getEndIndexInclusive(), currentSearchHighlightColor) };
}
}
public ViewerPosition getViewerPosition() {
return codeViewer.getViewerPosition();
return fieldPanel.getViewerPosition();
}
public void setViewerPosition(ViewerPosition viewerPosition) {
codeViewer.setViewerPosition(viewerPosition.getIndex(), viewerPosition.getXOffset(),
fieldPanel.setViewerPosition(viewerPosition.getIndex(), viewerPosition.getXOffset(),
viewerPosition.getYOffset());
}
@Override
public void requestFocus() {
codeViewer.requestFocus();
fieldPanel.requestFocus();
}
public void selectAll() {
BigInteger numIndexes = layoutMgr.getNumIndexes();
FieldSelection selection = new FieldSelection();
selection.addRange(BigInteger.ZERO, numIndexes);
codeViewer.setSelection(selection);
fieldPanel.setSelection(selection);
// fake it out that the selection was caused by the field panel GUI.
selectionChanged(selection, EventTrigger.GUI_ACTION);
}
@ -770,4 +841,102 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
repaint();
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class SearchHighlightFactory implements HighlightFactory {
@Override
public Highlight[] getHighlights(Field field, String text, int cursorTextOffset) {
if (currentSearchLocation == null) {
return new Highlight[0];
}
ClangTextField cField = (ClangTextField) field;
int highlightLine = cField.getLineNumber();
FieldLocation searchCursorLocation =
((FieldBasedSearchLocation) currentSearchLocation).getFieldLocation();
int searchLineNumber = searchCursorLocation.getIndex().intValue() + 1;
if (highlightLine != searchLineNumber) {
// only highlight the match on the actual line
return new Highlight[0];
}
return new Highlight[] { new Highlight(currentSearchLocation.getStartIndexInclusive(),
currentSearchLocation.getEndIndexInclusive(), currentSearchHighlightColor) };
}
}
/**
* A simple class that handles the animators callback to scroll the display
*/
private class ScrollingCallback implements SwingAnimationCallback {
private int startLine;
private int endLine;
private int endColumn;
private int duration;
ScrollingCallback(FieldLocation start, int endLineNumber, int endColumn, int distance) {
this.startLine = start.getIndex().intValue();
this.endLine = endLineNumber;
this.endColumn = endColumn;
// have things nearby execute more quickly so users don't wait needlessly
double rate = Math.pow(distance, .8);
int ms = (int) rate * 100;
this.duration = Math.min(1000, ms);
}
@Override
public int getDuration() {
return duration;
}
@Override
public void progress(double percentComplete) {
int length = Math.abs(endLine - startLine);
long offset = Math.round(length * percentComplete);
int current = 0;
if (startLine > endLine) {
// backwards
current = (int) (startLine - offset);
}
else {
current = (int) (startLine + offset);
}
FieldLocation location = new FieldLocation(BigInteger.valueOf(current));
fieldPanel.scrollTo(location);
}
@Override
public void done() {
fieldPanel.goTo(BigInteger.valueOf(endLine), 0, 0, endColumn, false);
}
}
private class DecompilerFieldPanel extends FieldPanel {
public DecompilerFieldPanel(LayoutModel model) {
super(model);
}
/**
* Moves this field panel to the given line and column. Further, this navigation will
* fire an event to the rest of the tool. (This is in contrast to a field panel
* <code>goTo</code>, which we use to simply move the cursor, but not trigger an
* tool-level navigation event.)
*
* @param lineNumber the line number
* @param column the column within the line
*/
void navigateTo(int lineNumber, int column) {
fieldPanel.goTo(BigInteger.valueOf(lineNumber), 0, 0, column, false,
EventTrigger.GUI_ACTION);
}
}
}

View File

@ -234,16 +234,17 @@ public class DecompilerUtils {
/**
* Find index of first field containing a ClangNode in tokenList
* @param tokenlist
* @param queryTokens the list of tokens of interest
* @param fields the universe of fields to check
* @return index of field, or -1
*/
public static int findIndexOfFirstField(List<ClangToken> tokenlist, Field[] fields) {
public static int findIndexOfFirstField(List<ClangToken> queryTokens, Field[] fields) {
for (int i = 0; i < fields.length; i++) {
ClangTextField f = (ClangTextField) fields[i];
List<ClangToken> tokenList = f.getTokens();
for (int j = 0; j < tokenList.size(); j++) {
ClangNode token = tokenList.get(j);
if (tokenlist.contains(token)) {
List<ClangToken> fieldTokens = f.getTokens();
for (int j = 0; j < fieldTokens.size(); j++) {
ClangNode fieldToken = fieldTokens.get(j);
if (queryTokens.contains(fieldToken)) {
return i;
}
}
@ -252,11 +253,10 @@ public class DecompilerUtils {
}
/**
* Find all ClangNodes that have a minimum address in
* the AddressSetView
* @param reslist is resulting list of found ClangNodes
* @param parentNode is root of node tree to search
* @param aset is the AddressSetView to match
* Find all ClangNodes that have a minimum address in the AddressSetView
* @param root the root of the token tree
* @param addressSet the addresses to restrict
* @return the list of tokens
*/
public static List<ClangToken> getTokens(ClangNode root, AddressSetView addressSet) {
List<ClangToken> tokenList = new ArrayList<>();
@ -512,6 +512,110 @@ public class DecompilerUtils {
return null;
}
public static ClangLabelToken getGoToTargetToken(ClangTokenGroup root, ClangLabelToken label) {
ClangNode parent = label.Parent();
if (!(parent instanceof ClangStatement)) {
return null;
}
ClangStatement statement = (ClangStatement) parent;
if (!isGoToStatement(statement)) {
return null;
}
String destinationStart = label.getText() + ':';
Address address = label.getMinAddress();
List<ClangToken> tokens = DecompilerUtils.getTokens(root, address);
for (ClangToken token : tokens) {
if (isGoToStatement(token)) {
continue; // ignore any goto statements
}
if (!(token instanceof ClangLabelToken)) {
continue;
}
ClangNode tokenParent = token.Parent();
String parentText = tokenParent.toString();
if (parentText.startsWith(destinationStart)) {
return (ClangLabelToken) token;
}
}
return null;
}
public static ClangSyntaxToken getMatchingBrace(ClangSyntaxToken startToken) {
ClangNode parent = startToken.Parent();
List<ClangNode> list = new ArrayList<>();
parent.flatten(list);
String text = startToken.getText();
boolean forward = "}".equals(text);
if (!forward) {
Collections.reverse(list);
}
Stack<ClangSyntaxToken> braceStack = new Stack<>();
for (int i = 0; i < list.size(); ++i) {
ClangToken token = (ClangToken) list.get(i);
if (token instanceof ClangSyntaxToken) {
ClangSyntaxToken syntaxToken = (ClangSyntaxToken) token;
if (startToken == syntaxToken) {
// found our starting token, take the current value on the stack
ClangSyntaxToken matchingBrace = braceStack.pop();
return matchingBrace;
}
if (!isBrace(syntaxToken)) {
continue;
}
if (braceStack.isEmpty()) {
braceStack.push(syntaxToken);
continue;
}
ClangSyntaxToken lastToken = braceStack.peek();
if (isMatchingBrace(lastToken, syntaxToken)) {
braceStack.pop();
}
else {
braceStack.push(syntaxToken);
}
}
}
return null;
}
public static boolean isMatchingBrace(ClangSyntaxToken braceToken,
ClangSyntaxToken otherBraceToken) {
String brace = braceToken.getText();
String otherBrace = otherBraceToken.getText();
return !brace.equals(otherBrace);
}
public static boolean isBrace(ClangSyntaxToken token) {
String text = token.getText();
return "{".equals(text) || "}".equals(text);
}
public static boolean isGoToStatement(ClangToken token) {
ClangNode parent = token.Parent();
if (!(parent instanceof ClangStatement)) {
return false;
}
return isGoToStatement((ClangStatement) parent);
}
private static boolean isGoToStatement(ClangStatement statement) {
String text = statement.toString();
return text.startsWith("goto");
}
public static ArrayList<ClangLine> toLines(ClangTokenGroup group) {
List<ClangNode> alltoks = new ArrayList<>();
@ -549,4 +653,5 @@ public class DecompilerUtils {
lines.add(current);
return lines;
}
}

View File

@ -79,12 +79,9 @@ public class DecompilePlugin extends Plugin {
* This happens when a readDataState occurs when a tool is restored
* or when switching program tabs.
*/
SwingUpdateManager delayedLocationUpdateMgr = new SwingUpdateManager(200, 200, new Runnable() {
@Override
public void run() {
if (currentLocation != null) {
connectedProvider.setLocation(currentLocation, null);
}
SwingUpdateManager delayedLocationUpdateMgr = new SwingUpdateManager(200, 200, () -> {
if (currentLocation != null) {
connectedProvider.setLocation(currentLocation, null);
}
});
@ -92,7 +89,7 @@ public class DecompilePlugin extends Plugin {
super(tool);
disconnectedProviders = new ArrayList<DecompilerProvider>();
disconnectedProviders = new ArrayList<>();
connectedProvider = new PrimaryDecompilerProvider(this);
createActions();

View File

@ -443,6 +443,9 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
@Override
public void locationChanged(ProgramLocation programLocation) {
if (programLocation.equals(currentLocation)) {
return;
}
currentLocation = programLocation;
contextChanged();
plugin.locationChanged(this, programLocation);

View File

@ -0,0 +1,83 @@
/* ###
* 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.decompile;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.util.OperandFieldLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.test.ClassicSampleX86ProgramBuilder;
public class DecompilerNavigationTest extends AbstractDecompilerTest {
@Before
@Override
public void setUp() throws Exception {
super.setUp();
CodeViewerProvider cbProvider = codeBrowser.getProvider();
tool.showComponentProvider(cbProvider, true);
}
@Override
protected Program getProgram() throws Exception {
return buildProgram();
}
private Program buildProgram() throws Exception {
ClassicSampleX86ProgramBuilder builder =
new ClassicSampleX86ProgramBuilder("notepad", false, this);
// need a default label at 01002cf0, so make up a reference
builder.createMemoryReference("01002ce5", "01002cf0", RefType.FALL_THROUGH,
SourceType.ANALYSIS);
return builder.getProgram();
}
@Test
public void testNavigation_ExternalEventDoesNotTriggerNavigation() {
//
// Test to make sure that external ProgramLocationEvent notifications to not trigger
// the Decompiler to broadcast a new event. Setup a tool with the Listing and
// the Decompiler open. Then, navigate in the Listing and verify the address does not
// move. (This is somewhat subject to the Code Unit at the address in how the
// Decompiler itself responds to the incoming event.)
//
// very specific location within the instruction that is known to affect how the
// decompiler responds
String operandPrefix = "dword ptr [EBP + ";
String operandReferenceName = "destStr]";
OperandFieldLocation operandLocation = new OperandFieldLocation(program, addr("0100416c"),
null, addr("0x8"), operandPrefix + operandReferenceName, 1, 9);
codeBrowser.goTo(operandLocation);
waitForSwing();
ProgramLocation currentLocation = codeBrowser.getCurrentLocation();
assertTrue(currentLocation instanceof OperandFieldLocation);
assertEquals(operandLocation.getAddress(), currentLocation.getAddress());
}
}

View File

@ -23,16 +23,12 @@ import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.ToyProgramBuilder;
import ghidra.util.task.TaskMonitorAdapter;
import ghidra.util.task.TaskMonitor;
public class DecompilerTest extends AbstractGhidraHeadedIntegrationTest {
private Program prog;
private DecompInterface decompiler;
public DecompilerTest() {
super();
}
@Before
public void setUp() throws Exception {
@ -57,9 +53,8 @@ public class DecompilerTest extends AbstractGhidraHeadedIntegrationTest {
public void testDecompileInterfaceReturnsAFunction() throws Exception {
Address addr = prog.getAddressFactory().getDefaultAddressSpace().getAddress(0x0);
Function func = prog.getListing().getFunctionAt(addr);
DecompileResults decompResults =
decompiler.decompileFunction(func, DecompileOptions.SUGGESTED_DECOMPILE_TIMEOUT_SECS,
TaskMonitorAdapter.DUMMY_MONITOR);
DecompileResults decompResults = decompiler.decompileFunction(func,
DecompileOptions.SUGGESTED_DECOMPILE_TIMEOUT_SECS, TaskMonitor.DUMMY);
String decompilation = decompResults.getDecompiledFunction().getC();
Assert.assertNotNull(decompilation);
}

View File

@ -220,8 +220,8 @@ public class AnimationUtils {
// note: instead of checking for 'animationEnabled' here, it will happen in the driver
// so that the we can call SwingAnimationCallback.done(), which will let the client
// perform its final action.
SwingAnimationCallbackDriver driver = new SwingAnimationCallbackDriver(callback, 1000);
int duration = callback.getDuration();
SwingAnimationCallbackDriver driver = new SwingAnimationCallbackDriver(callback, duration);
return driver.animator;
}

View File

@ -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.
@ -35,4 +34,14 @@ public interface SwingAnimationCallback {
* finalization work.
*/
public void done();
/**
* Returns the duration of this callback. The default is <code>1000 ms</code>. Subclasses
* can override this as needed.
*
* @return the duration
*/
public default int getDuration() {
return 1000;
}
}

View File

@ -197,7 +197,7 @@ public class FieldPanel extends JPanel
@Override
public void scrollLineDown() {
layouts = layoutHandler.ShiftViewDownOneRow();
layouts = layoutHandler.shiftViewDownOneRow();
notifyScrollListenerViewChangedAndRepaint();
}
@ -295,6 +295,51 @@ public class FieldPanel extends JPanel
return new ArrayList<>(layouts);
}
/**
* Returns true if the given field location is rendered on the screen; false if scrolled
* offscreen
*
* @param location the location to check
* @return true if the location is on the screen
*/
public boolean isLocationVisible(FieldLocation location) {
if (location == null) {
return false;
}
BigInteger locationIndex = location.getIndex();
for (AnchoredLayout layout : layouts) {
if (layout.getIndex().equals(locationIndex)) {
return true;
}
}
return false;
}
/**
* Returns the first visible layout or null if there are no visible layouts
*
* @return the first visible layout
*/
public AnchoredLayout getVisibleStartLayout() {
if (layouts.isEmpty()) {
return null;
}
return layouts.get(0);
}
/**
* Returns the last visible layout or null if there are no visible layouts
*
* @return the last visible layout
*/
public AnchoredLayout getVisibleEndLayout() {
if (layouts.isEmpty()) {
return null;
}
return layouts.get(layouts.size() - 1);
}
@Override
public void repaint() {
repaintPosted = true;
@ -714,13 +759,19 @@ public class FieldPanel extends JPanel
* the row in the field to go to.
* @param col
* the column in the field to go to.
* @param centerCursor
* @param alwaysCenterCursor
* if true, centers cursor on screen. Otherwise, only centers
* cursor if cursor is offscreen.
*/
public void goTo(BigInteger index, int fieldNum, int row, int col, boolean alwaysCenterCursor) {
goTo(index, fieldNum, row, col, alwaysCenterCursor, EventTrigger.API_CALL);
}
if (!cursorHandler.doSetCursorPosition(index, fieldNum, row, col, EventTrigger.API_CALL)) {
// for subclasses to control the event trigger
protected void goTo(BigInteger index, int fieldNum, int row, int col,
boolean alwaysCenterCursor, EventTrigger trigger) {
if (!cursorHandler.doSetCursorPosition(index, fieldNum, row, col, trigger)) {
return;
}

View File

@ -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.
@ -27,14 +26,15 @@ public class AnchoredLayoutHandler {
private final LayoutModel model;
private int viewHeight;
private final LinkedList<AnchoredLayout> layouts = new LinkedList<AnchoredLayout>();
private final LinkedList<AnchoredLayout> layouts = new LinkedList<>();
public AnchoredLayoutHandler(LayoutModel model, int viewHeight) {
this.model = model;
this.model = model;
this.viewHeight = viewHeight;
}
public List<AnchoredLayout> positionLayoutsAroundAnchor(BigInteger anchorIndex, int viewPosition) {
public List<AnchoredLayout> positionLayoutsAroundAnchor(BigInteger anchorIndex,
int viewPosition) {
layouts.clear();
AnchoredLayout layout = getClosestLayout(anchorIndex, viewPosition);
@ -42,12 +42,12 @@ public class AnchoredLayoutHandler {
layouts.add(layout);
fillOutLayouts();
}
return new ArrayList<AnchoredLayout>(layouts);
return new ArrayList<>(layouts);
}
public List<AnchoredLayout> ShiftViewDownOneRow() {
public List<AnchoredLayout> shiftViewDownOneRow() {
if (layouts.isEmpty()) {
return new ArrayList<AnchoredLayout>();
return new ArrayList<>();
}
AnchoredLayout layout = layouts.getFirst();
int yPos = layout.getYPos();
@ -57,7 +57,7 @@ public class AnchoredLayoutHandler {
public List<AnchoredLayout> shiftViewUpOneRow() {
if (layouts.isEmpty()) {
return new ArrayList<AnchoredLayout>();
return new ArrayList<>();
}
int scrollAmount = 0;
AnchoredLayout layout = layouts.getFirst();
@ -67,7 +67,7 @@ public class AnchoredLayoutHandler {
if (yPos == 0) {
layout = getPreviousLayout(index, yPos);
if (layout == null) {
return new ArrayList<AnchoredLayout>(layouts);
return new ArrayList<>(layouts);
}
layouts.add(0, layout);
yPos = layout.getYPos();
@ -77,20 +77,18 @@ public class AnchoredLayoutHandler {
return shiftView(scrollAmount);
}
public List<AnchoredLayout> shiftViewDownOnePage() {
if (layouts.isEmpty()) {
return new ArrayList<AnchoredLayout>();
return new ArrayList<>();
}
AnchoredLayout last = layouts.getLast();
int diff = last.getScrollableUnitIncrement(viewHeight-last.getYPos(), -1);
return shiftView(viewHeight+diff);
int diff = last.getScrollableUnitIncrement(viewHeight - last.getYPos(), -1);
return shiftView(viewHeight + diff);
}
public List<AnchoredLayout> shiftViewUpOnePage() {
if (layouts.isEmpty()) {
return new ArrayList<AnchoredLayout>();
return new ArrayList<>();
}
int scrollAmount = viewHeight;
AnchoredLayout first = layouts.getFirst();
@ -104,24 +102,24 @@ public class AnchoredLayoutHandler {
first = layouts.getFirst();
if (first.getYPos() != 0) {
return ShiftViewDownOneRow();
return shiftViewDownOneRow();
}
return new ArrayList<AnchoredLayout>(layouts);
return new ArrayList<>(layouts);
}
public List<AnchoredLayout> shiftView(int viewAmount) {
repositionLayouts(-viewAmount);
fillOutLayouts();
return new ArrayList<AnchoredLayout>(layouts);
return new ArrayList<>(layouts);
}
public List<AnchoredLayout> setViewHeight(int viewHeight) {
this.viewHeight = viewHeight;
this.viewHeight = viewHeight;
if (layouts.isEmpty()) {
return positionLayoutsAroundAnchor(BigInteger.ZERO, 0);
}
fillOutLayouts();
return new ArrayList<AnchoredLayout>(layouts);
return new ArrayList<>(layouts);
}
private void fillOutLayouts() {
@ -129,46 +127,46 @@ public class AnchoredLayoutHandler {
return;
}
AnchoredLayout lastLayout = layouts.getLast();
fillLayoutsForward(lastLayout.getIndex(), lastLayout.getYPos()+lastLayout.getHeight());
fillLayoutsForward(lastLayout.getIndex(), lastLayout.getYPos() + lastLayout.getHeight());
lastLayout = layouts.getLast();
if (lastLayout.getEndY() < viewHeight) {
repositionLayouts(viewHeight - lastLayout.getEndY());
}
AnchoredLayout firstLayout = layouts.getFirst();
fillLayoutsBack(firstLayout.getIndex(), firstLayout.getYPos());
firstLayout = layouts.getFirst();
if (firstLayout.getYPos() > 0) {
repositionLayouts(-firstLayout.getYPos());
}
lastLayout = layouts.getLast();
fillLayoutsForward(lastLayout.getIndex(), lastLayout.getYPos()+lastLayout.getHeight());
fillLayoutsForward(lastLayout.getIndex(), lastLayout.getYPos() + lastLayout.getHeight());
trimLayouts();
}
private void repositionLayouts(int amount) {
for (AnchoredLayout layout : layouts) {
layout.setYPos(layout.getYPos()+amount);
layout.setYPos(layout.getYPos() + amount);
}
}
private void trimLayouts() {
Iterator<AnchoredLayout> it = layouts.iterator();
while(it.hasNext()) {
while (it.hasNext()) {
AnchoredLayout layout = it.next();
int y = layout.getYPos();
int height = layout.getHeight();
if ( (y+height <= 0) || (y > viewHeight) ) {
if ((y + height <= 0) || (y > viewHeight)) {
it.remove();
}
}
}
private void fillLayoutsForward(BigInteger existingLastIndex, int y) {
BigInteger index = existingLastIndex;
while(y < viewHeight) {
while (y < viewHeight) {
AnchoredLayout nextLayout = getNextLayout(index, y);
if (nextLayout == null) {
return;
@ -178,9 +176,10 @@ public class AnchoredLayoutHandler {
index = nextLayout.getIndex();
}
}
private void fillLayoutsBack(BigInteger existingFirstIndex, int y) {
BigInteger index = existingFirstIndex;
while(y > 0) {
while (y > 0) {
AnchoredLayout prevLayout = getPreviousLayout(index, y);
if (prevLayout == null) {
return;
@ -192,17 +191,17 @@ public class AnchoredLayoutHandler {
}
private AnchoredLayout getPreviousLayout(BigInteger index, int yPos) {
while((index = model.getIndexBefore(index)) != null) {
while ((index = model.getIndexBefore(index)) != null) {
Layout layout = model.getLayout(index);
if (layout != null) {
return new AnchoredLayout(layout, index, yPos-layout.getHeight());
return new AnchoredLayout(layout, index, yPos - layout.getHeight());
}
}
return null;
}
private AnchoredLayout getNextLayout(BigInteger index, int yPos) {
while((index = model.getIndexAfter(index)) != null) {
while ((index = model.getIndexAfter(index)) != null) {
Layout layout = model.getLayout(index);
if (layout != null) {
return new AnchoredLayout(layout, index, yPos);
@ -210,7 +209,6 @@ public class AnchoredLayoutHandler {
}
return null;
}
private AnchoredLayout getClosestLayout(BigInteger index, int y) {
Layout layout = model.getLayout(index);