Issue #302 better Ctrl+Click handling - underline and cursor hand only when can go through

This commit is contained in:
Jindra Petřík
2014-10-29 20:08:16 +01:00
parent d7bbfe265d
commit abb173f58d
5 changed files with 476 additions and 18 deletions

View File

@@ -109,8 +109,10 @@ import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.text.Highlighter;
import javax.swing.tree.TreePath;
import jsyntaxpane.DefaultSyntaxKit;
import jsyntaxpane.Token;
public class ABCPanel extends JPanel implements ItemListener, ActionListener, SearchListener<ABCPanelSearchResult>, Freed {
@@ -362,6 +364,24 @@ public class ABCPanel extends JPanel implements ItemListener, ActionListener, Se
setLayout(new BorderLayout());
decompiledTextArea = new DecompiledEditorPane(this);
decompiledTextArea.setLinkHandler(new LinkHandler() {
@Override
public boolean isLink(Token token) {
return isDeclaration(token.start);
}
@Override
public void handleLink(Token token) {
gotoDeclaration(token.start);
}
@Override
public Highlighter.HighlightPainter linkPainter() {
return decompiledTextArea.linkPainter();
}
});
searchPanel = new SearchPanel<>(new FlowLayout(), this);
@@ -439,7 +459,7 @@ public class ABCPanel extends JPanel implements ItemListener, ActionListener, Se
View.addEditorAction(decompiledTextArea, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
int multinameIndex = decompiledTextArea.getMultinameUnderCursor();
int multinameIndex = decompiledTextArea.getMultinameUnderCaret();
if (multinameIndex > -1) {
UsageFrame usageFrame = new UsageFrame(swf.abcList, abc, multinameIndex, ABCPanel.this, false);
usageFrame.setVisible(true);
@@ -450,7 +470,7 @@ public class ABCPanel extends JPanel implements ItemListener, ActionListener, Se
View.addEditorAction(decompiledTextArea, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
gotoDeclaration();
gotoDeclaration(decompiledTextArea.getCaretPosition());
}
}, "find-declaration", AppStrings.translate("abc.action.find-declaration"), "control B");
@@ -518,8 +538,36 @@ public class ABCPanel extends JPanel implements ItemListener, ActionListener, Se
tabbedPane.addTab(AppStrings.translate("constants"), panConstants);
}
private void gotoDeclaration() {
int multinameIndex = decompiledTextArea.getMultinameUnderCursor();
private boolean isDeclaration(int pos) {
int multinameIndex = decompiledTextArea.getMultinameAtPos(pos);
if (multinameIndex > -1) {
List<MultinameUsage> usages = abc.findMultinameDefinition(swf.abcList, multinameIndex);
Multiname m = abc.constants.constant_multiname.get(multinameIndex);
//search other ABC tags if this is not private multiname
if (m.namespace_index > 0 && abc.constants.constant_namespace.get(m.namespace_index).kind != Namespace.KIND_PRIVATE) {
for (ABCContainerTag at : swf.abcList) {
ABC a = at.getABC();
if (a == abc) {
continue;
}
int mid = a.constants.getMultinameId(m, false);
if (mid > 0) {
usages.addAll(a.findMultinameDefinition(swf.abcList, mid));
}
}
}
//more than one? display list
if (!usages.isEmpty()) {
return true;
}
}
return false;
}
private void gotoDeclaration(int pos) {
int multinameIndex = decompiledTextArea.getMultinameAtPos(pos);
if (multinameIndex > -1) {
List<MultinameUsage> usages = abc.findMultinameDefinition(swf.abcList, multinameIndex);
@@ -556,7 +604,7 @@ public class ABCPanel extends JPanel implements ItemListener, ActionListener, Se
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == 17 && !decompiledTextArea.isEditable()) {
ctrlDown = true;
decompiledTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
//decompiledTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
@@ -564,7 +612,7 @@ public class ABCPanel extends JPanel implements ItemListener, ActionListener, Se
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == 17) {
ctrlDown = false;
decompiledTextArea.setCursor(Cursor.getDefaultCursor());
//decompiledTextArea.setCursor(Cursor.getDefaultCursor());
}
}
@@ -572,8 +620,8 @@ public class ABCPanel extends JPanel implements ItemListener, ActionListener, Se
public void mouseClicked(MouseEvent e) {
if (ctrlDown && e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1 && !decompiledTextArea.isEditable()) {
ctrlDown = false;
decompiledTextArea.setCursor(Cursor.getDefaultCursor());
gotoDeclaration();
//decompiledTextArea.setCursor(Cursor.getDefaultCursor());
//gotoDeclaration();
}
}

View File

@@ -34,6 +34,7 @@ import com.jpexs.decompiler.flash.helpers.HilightedTextWriter;
import com.jpexs.decompiler.flash.helpers.hilight.Highlighting;
import com.jpexs.decompiler.flash.tags.ABCContainerTag;
import com.jpexs.helpers.Cache;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
@@ -220,12 +221,24 @@ public class DecompiledEditorPane extends LineMarkedEditorPane implements CaretL
caretUpdate(null);
reset = false;
}
public int getMultinameUnderCursor() {
int pos = getCaretPosition();
public int getMultinameUnderMouseCursor(Point pt) {
return getMultinameAtPos(viewToModel(pt));
}
public int getMultinameUnderCaret() {
return getMultinameAtPos(getCaretPosition());
}
public int getMultinameAtPos(int pos) {
Highlighting tm = Highlighting.search(methodHighlights, pos);
if (tm == null) {
return -1;
}
int mi = (int)(long)tm.getPropertyLong("index");
int bi = abc.findBodyIndex(mi);
Highlighting h = Highlighting.search(highlights, pos);
if (h != null) {
List<AVM2Instruction> list = abc.bodies.get(abcPanel.detailPanel.methodTraitPanel.methodCodePanel.getBodyIndex()).getCode().code;
List<AVM2Instruction> list = abc.bodies.get(bi).getCode().code;
AVM2Instruction lastIns = null;
long inspos = 0;
AVM2Instruction selIns = null;

View File

@@ -19,20 +19,45 @@ package com.jpexs.decompiler.flash.gui.abc;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.gui.AppStrings;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.KeyEventPostProcessor;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.regex.Pattern;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.plaf.TextUI;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.Highlighter;
import javax.swing.text.Highlighter.HighlightPainter;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.View;
import javax.swing.text.html.HTMLEditorKit;
import jsyntaxpane.SyntaxDocument;
import jsyntaxpane.SyntaxStyle;
import jsyntaxpane.SyntaxStyles;
import jsyntaxpane.Token;
import jsyntaxpane.actions.ActionUtils;
import jsyntaxpane.components.Markers;
/**
*
* @author JPEXS
*/
public class LineMarkedEditorPane extends UndoFixedEditorPane {
public class LineMarkedEditorPane extends UndoFixedEditorPane implements LinkHandler {
private static final int truncateLimit = 8192;
private int lastLine = -1;
@@ -96,8 +121,154 @@ public class LineMarkedEditorPane extends UndoFixedEditorPane {
error = false;
repaint();
}
}
});
final LinkAdapter la = new LinkAdapter();
addMouseMotionListener(la);
addMouseListener(la);
//No standard AddKeyListener as we want to catch Ctrl globally no matter of focus
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addKeyEventPostProcessor(new KeyEventPostProcessor() {
@Override
public boolean postProcessKeyEvent(KeyEvent e) {
if (e.getID() == KeyEvent.KEY_PRESSED) {
la.keyPressed(e);
}
if (e.getID() == KeyEvent.KEY_RELEASED) {
la.keyReleased(e);
}
if (e.getID() == KeyEvent.KEY_TYPED) {
la.keyTyped(e);
}
return false;
}
});
}
private Token lastUnderlined = null;
private static final HighlightPainter underLinePainter = new UnderLinePainter(new Color(0, 0, 255));
private LinkHandler linkHandler = this;
private class LinkAdapter extends MouseAdapter implements KeyListener {
private Point lastPos = new Point(0, 0);
private boolean ctrlDown = false;
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
ctrlDown = true;
update();
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
ctrlDown = false;
update();
}
}
private void update() {
if (ctrlDown) {
Document d = getDocument();
if (d instanceof SyntaxDocument) {
SyntaxDocument sd = (SyntaxDocument) d;
int pos = viewToModel(lastPos);
if (pos < 0) {
return;
}
Token t = sd.getTokenAt(pos);
if (t != lastUnderlined) {
if (t == null || lastUnderlined == null || !t.equals(lastUnderlined)) {
MyMarkers.removeMarkers(LineMarkedEditorPane.this, underLinePainter);
if (t != null && linkHandler.isLink(t)) {
lastUnderlined = t;
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
lastUnderlined = null;
}
} else {
lastUnderlined = null;
}
}
if (lastUnderlined != null) {
MyMarkers.markToken(LineMarkedEditorPane.this, lastUnderlined, underLinePainter);
} else {
setCursor(Cursor.getDefaultCursor());
}
repaint();
}
}else {
lastUnderlined = null;
MyMarkers.removeMarkers(LineMarkedEditorPane.this, underLinePainter);
setCursor(Cursor.getDefaultCursor());
repaint();
}
}
@Override
public void mouseClicked(MouseEvent e) {
if (ctrlDown) {
SyntaxDocument sd = (SyntaxDocument) getDocument();
int pos = viewToModel(e.getPoint());
if (pos < 0) {
return;
}
Token t = sd.getTokenAt(pos);
if (t != null && linkHandler.isLink(t)) {
linkHandler.handleLink(t);
}
}
}
@Override
public void mouseMoved(MouseEvent e) {
ctrlDown = (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) > 0;
lastPos = e.getPoint();
update();
}
}
public void setLinkHandler(LinkHandler linkHandler) {
if (linkHandler == null) {
linkHandler = this;
}
this.linkHandler = linkHandler;
}
public LinkHandler getLinkHandler() {
return linkHandler;
}
@Override
public HighlightPainter linkPainter() {
return underLinePainter;
}
@Override
public boolean isLink(Token token) {
return false;
}
@Override
public void handleLink(Token token) {
}
@Override
@@ -116,7 +287,80 @@ public class LineMarkedEditorPane extends UndoFixedEditorPane {
lastLine = -1;
error = true;
super.setText(t, contentType);
setCaretPosition(0); //scroll to top
setCaretPosition(0); //scroll to top
}
public static class UnderLinePainter extends DefaultHighlighter.DefaultHighlightPainter {
public UnderLinePainter(Color color) {
super(color);
}
@Override
public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
try {
// --- determine locations ---
TextUI mapper = c.getUI();
Color col = getColor();
if (col == null) {
col = Color.black;
}
g.setColor(col);
for (int i = offs0; i < offs1; i++) {
Rectangle r = mapper.modelToView(c, i, Position.Bias.Forward);
Rectangle r1 = mapper.modelToView(c, i + 1, Position.Bias.Forward);
if (r1.y == r.y) {
g.drawLine(r.x, r.y + r.height - 3, r1.x, r.y + r.height - 3);
}
}
} catch (BadLocationException e) {
// can't render
}
}
@Override
public Shape paintLayer(Graphics g, int offs0, int offs1,
Shape bounds, JTextComponent c, View view) {
g.setColor(c.getSelectionColor());
Rectangle r;
if (offs0 == view.getStartOffset()
&& offs1 == view.getEndOffset()) {
// Contained in view, can just use bounds.
if (bounds instanceof Rectangle) {
r = (Rectangle) bounds;
} else {
r = bounds.getBounds();
}
} else {
// Should only render part of View.
try {
// --- determine locations ---
Shape shape = view.modelToView(offs0, Position.Bias.Forward,
offs1, Position.Bias.Backward,
bounds);
r = (shape instanceof Rectangle)
? (Rectangle) shape : shape.getBounds();
} catch (BadLocationException e) {
// can't render
r = null;
}
}
if (r != null) {
r.width = Math.max(r.width, 1);
paint(g, offs0, offs1, r, c);
}
return r;
}
}
@Override
@@ -126,16 +370,13 @@ public class LineMarkedEditorPane extends UndoFixedEditorPane {
if (lastLine > 0) {
FontMetrics fontMetrics = g.getFontMetrics();
int lh = fontMetrics.getHeight();
int a = fontMetrics.getAscent();
int d = fontMetrics.getDescent();
int h = a + d;
int rH = h;
if (error) {
g.setColor(new Color(255, 200, 200));
} else {
g.setColor(new Color(0xee, 0xee, 0xee));
}
g.fillRect(0, d + lh * lastLine - 1, getWidth(), lh);
g.fillRect(0, d + lh * lastLine - 1, getWidth(), lh);
}
super.paint(g);
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2014 jindr_000
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.gui.abc;
import javax.swing.text.Highlighter;
import jsyntaxpane.Token;
/**
*
* @author JPEXS
*/
public interface LinkHandler {
public boolean isLink(Token token);
public void handleLink(Token token);
public Highlighter.HighlightPainter linkPainter();
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright (C) 2014 jindr_000
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.gui.abc;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.text.BadLocationException;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import jsyntaxpane.SyntaxDocument;
import jsyntaxpane.Token;
import jsyntaxpane.actions.ActionUtils;
/**
*
* @author JPEXS
*/
public class MyMarkers {
/**
* Removes only our private highlights
* This is public so that we can remove the highlights when the editorKit
* is unregistered. SimpleMarker can be null, in which case all instances of
* our Markers are removed.
* @param component the text component whose markers are to be removed
* @param marker the SimpleMarker to remove
*/
public static void removeMarkers(JTextComponent component, Highlighter.HighlightPainter marker) {
Highlighter hilite = component.getHighlighter();
Highlighter.Highlight[] hilites = hilite.getHighlights();
for (int i = 0; i < hilites.length; i++) {
Highlighter.HighlightPainter hMarker = hilites[i].getPainter();
if (marker == null || hMarker.equals(marker)) {
hilite.removeHighlight(hilites[i]);
}
}
}
/**
* Remove all the markers from an JEditorPane
* @param editorPane
*/
public static void removeMarkers(JTextComponent editorPane) {
removeMarkers(editorPane, null);
}
/**
* add highlights for the given Token on the given pane
* @param pane
* @param token
* @param marker
*/
public static void markToken(JTextComponent pane, Token token, Highlighter.HighlightPainter marker) {
markText(pane, token.start, token.end(), marker);
}
/**
* add highlights for the given region on the given pane
* @param pane
* @param start
* @param end
* @param marker
*/
public static void markText(JTextComponent pane, int start, int end, Highlighter.HighlightPainter marker) {
try {
Highlighter hiliter = pane.getHighlighter();
int selStart = pane.getSelectionStart();
int selEnd = pane.getSelectionEnd();
// if there is no selection or selection does not overlap
if(selStart == selEnd || end < selStart || start > selStart) {
hiliter.addHighlight(start, end, marker);
return;
}
// selection starts within the highlight, highlight before slection
if(selStart > start && selStart < end ) {
hiliter.addHighlight(start, selStart, marker);
}
// selection ends within the highlight, highlight remaining
if(selEnd > start && selEnd < end ) {
hiliter.addHighlight(selEnd, end, marker);
}
} catch (BadLocationException ex) {
}
}
/**
* Mark all text in the document that matches the given pattern
* @param pane control to use
* @param pattern pattern to match
* @param marker marker to use for highlighting
*/
public static void markAll(JTextComponent pane, Pattern pattern, Highlighter.HighlightPainter marker) {
SyntaxDocument sDoc = ActionUtils.getSyntaxDocument(pane);
if(sDoc == null || pattern == null) {
return;
}
Matcher matcher = sDoc.getMatcher(pattern);
// we may not have any matcher (due to undo or something, so don't do anything.
if(matcher==null) {
return;
}
while(matcher.find()) {
markText(pane, matcher.start(), matcher.end(), marker);
}
}
}