From abb173f58d13ce5e63797a5b76d4aea2c1bdfae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Wed, 29 Oct 2014 20:08:16 +0100 Subject: [PATCH] Issue #302 better Ctrl+Click handling - underline and cursor hand only when can go through --- .../decompiler/flash/gui/abc/ABCPanel.java | 64 ++++- .../flash/gui/abc/DecompiledEditorPane.java | 21 +- .../flash/gui/abc/LineMarkedEditorPane.java | 253 +++++++++++++++++- .../decompiler/flash/gui/abc/LinkHandler.java | 30 +++ .../decompiler/flash/gui/abc/MyMarkers.java | 126 +++++++++ 5 files changed, 476 insertions(+), 18 deletions(-) create mode 100644 src/com/jpexs/decompiler/flash/gui/abc/LinkHandler.java create mode 100644 src/com/jpexs/decompiler/flash/gui/abc/MyMarkers.java diff --git a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java index 2f41afc38..c6b1461f9 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java @@ -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, 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 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 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(); } } diff --git a/src/com/jpexs/decompiler/flash/gui/abc/DecompiledEditorPane.java b/src/com/jpexs/decompiler/flash/gui/abc/DecompiledEditorPane.java index 768e035bd..44fcc3a16 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/DecompiledEditorPane.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/DecompiledEditorPane.java @@ -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 list = abc.bodies.get(abcPanel.detailPanel.methodTraitPanel.methodCodePanel.getBodyIndex()).getCode().code; + List list = abc.bodies.get(bi).getCode().code; AVM2Instruction lastIns = null; long inspos = 0; AVM2Instruction selIns = null; diff --git a/src/com/jpexs/decompiler/flash/gui/abc/LineMarkedEditorPane.java b/src/com/jpexs/decompiler/flash/gui/abc/LineMarkedEditorPane.java index 3a146b3e0..35ff92403 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/LineMarkedEditorPane.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/LineMarkedEditorPane.java @@ -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); } diff --git a/src/com/jpexs/decompiler/flash/gui/abc/LinkHandler.java b/src/com/jpexs/decompiler/flash/gui/abc/LinkHandler.java new file mode 100644 index 000000000..461f2b49a --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/abc/LinkHandler.java @@ -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 . + */ +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(); +} diff --git a/src/com/jpexs/decompiler/flash/gui/abc/MyMarkers.java b/src/com/jpexs/decompiler/flash/gui/abc/MyMarkers.java new file mode 100644 index 000000000..a1420a33e --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/abc/MyMarkers.java @@ -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 . + */ +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); + } + } + + + +}