mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-25 05:32:14 +00:00
GT-2846, 2847 - Decompiler - updated Decompiler to allow double-click
navigation of 'goto' statements and brace pairs
This commit is contained in:
parent
fb3975e4ef
commit
175da7cf02
@ -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();
|
||||
@ -115,6 +119,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);
|
||||
@ -523,15 +537,16 @@ 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;
|
||||
}
|
||||
|
||||
layoutCache.clear();
|
||||
|
||||
boolean updateImmediately = ev.numRecords() <= 5;
|
||||
notifyDataChanged(updateImmediately);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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->Cursor Text Highlight</B>).</LI>
|
||||
</UL>
|
||||
|
||||
<P><IMG alt="" src="../../shared/note.png" border="0"> If you press the middle mouse
|
||||
button (the button changed in the tool options under <B>Browser Field->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>
|
||||
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
@ -140,8 +141,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
@ -219,21 +220,75 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
||||
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) {
|
||||
codeViewer.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) {
|
||||
codeViewer.goTo(BigInteger.valueOf(lineNumber), 0, 0, column, false);
|
||||
return;
|
||||
}
|
||||
|
||||
ScrollingCallback callback = new ScrollingCallback(start, lineNumber, column, distance);
|
||||
AnimationUtils.executeSwingAnimationCallback(callback);
|
||||
}
|
||||
|
||||
private int getOffscreenDistance(int line) {
|
||||
|
||||
AnchoredLayout start = codeViewer.getVisibleStartLayout();
|
||||
int visibleStartLine = start.getIndex().intValue();
|
||||
if (visibleStartLine > line) {
|
||||
// the end is off the top of the screen
|
||||
return visibleStartLine - line;
|
||||
}
|
||||
|
||||
AnchoredLayout end = codeViewer.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();
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -514,7 +608,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
||||
return;
|
||||
}
|
||||
|
||||
if (trigger == EventTrigger.GUI_ACTION) {
|
||||
if (trigger != EventTrigger.INTERNAL_ONLY) {
|
||||
ProgramLocation programLocation = getProgramLocation(field, location);
|
||||
if (programLocation != null) {
|
||||
controller.locationChanged(programLocation);
|
||||
@ -702,30 +796,6 @@ 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();
|
||||
}
|
||||
@ -770,4 +840,82 @@ 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));
|
||||
codeViewer.scrollTo(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void done() {
|
||||
codeViewer.goTo(BigInteger.valueOf(endLine), 0, 0, endColumn, false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
* @return the first visible layout
|
||||
*/
|
||||
public AnchoredLayout getVisibleStartLayout() {
|
||||
if (layouts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return layouts.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last visible layout
|
||||
*
|
||||
* @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,7 +759,7 @@ 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.
|
||||
*/
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user