diff --git a/CHANGELOG.md b/CHANGELOG.md index cc6d592b5..739ef8255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file. - AS1/2 - highlight variable definition and all its instances on cursor place (also in edit mode) - AS1/2 - underline errors in the code (also in edit mode) +- AS1/2 - highlight variables and errors on scrollbar ### Changed - AS1/2 - Single DoAction tag inside frame is now displayed directly as frame node diff --git a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java index e04bedf9e..417d9582f 100644 --- a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java @@ -57,7 +57,6 @@ import com.jpexs.decompiler.flash.gui.ViewMessages; import com.jpexs.decompiler.flash.gui.controls.JPersistentSplitPane; import com.jpexs.decompiler.flash.gui.controls.NoneSelectedButtonGroup; import com.jpexs.decompiler.flash.gui.editor.DebuggableEditorPane; -import com.jpexs.decompiler.flash.gui.editor.LinkHandler; import com.jpexs.decompiler.flash.gui.tagtree.AbstractTagTreeModel; import com.jpexs.decompiler.flash.helpers.HighlightedText; import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; @@ -96,7 +95,6 @@ import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JCheckBox; -import javax.swing.JCheckBoxMenuItem; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -111,7 +109,6 @@ import javax.swing.border.EmptyBorder; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.text.BadLocationException; -import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; import javax.swing.text.Utilities; import javax.swing.tree.TreePath; @@ -947,8 +944,11 @@ public class ActionPanel extends JPanel implements SearchListener tokenTypes = new HashSet<>(); private Markers.SimpleMarker marker; @@ -81,6 +98,14 @@ public class ActionVariableMarker implements SyntaxComponent, CaretListener, Pro private MouseMotionAdapter mouseMotionAdapter; + private JLayer layer; + + private ScrollbarOverlay scrollbarOverlay; + + private ScrollPaneUI originalScrollPaneUI; + + private ScrollBarUI originalScrollBarUI; + /** * Constructs a new Token highlighter */ @@ -106,6 +131,7 @@ public class ActionVariableMarker implements SyntaxComponent, CaretListener, Pro if (token != null && tokenTypes.contains(token.type)) { addMarkers(token); } + layer.repaint(); } } @@ -114,11 +140,13 @@ public class ActionVariableMarker implements SyntaxComponent, CaretListener, Pro */ public void removeMarkers() { Markers.removeMarkers(pane, marker); + scrollbarOverlay.removeMarkers(SCROLLBAR_VARIABLE_COLOR); } public void removeErrorMarkers() { Markers.removeMarkers(pane, errorMarker); errorsShown = false; + scrollbarOverlay.removeMarkers(SCROLLBAR_ERROR_COLOR); } private Token getIdentifierTokenAt(SyntaxDocument sDoc, int pos) { @@ -173,8 +201,10 @@ public class ActionVariableMarker implements SyntaxComponent, CaretListener, Pro Token token = getNearestTokenAt(doc, position); if (token != null) { Markers.markToken(pane, token, errorMarker); + markPositionOnScrollbar(position, SCROLLBAR_ERROR_COLOR); } } + layer.repaint(); doc.readUnlock(); errorsShown = true; } @@ -195,10 +225,12 @@ public class ActionVariableMarker implements SyntaxComponent, CaretListener, Pro if (definitionToken != null) { if (definitionPosToReferences.containsKey(definitionPos)) { Markers.markToken(pane, definitionToken, marker); + markPositionOnScrollbar(definitionToken.start, SCROLLBAR_VARIABLE_COLOR); for (int i : definitionPosToReferences.get(definitionPos)) { Token referenceToken = getIdentifierTokenAt(sDoc, i); if (referenceToken != null) { Markers.markToken(pane, referenceToken, marker); + markPositionOnScrollbar(referenceToken.start, SCROLLBAR_VARIABLE_COLOR); } } } @@ -206,12 +238,20 @@ public class ActionVariableMarker implements SyntaxComponent, CaretListener, Pro sDoc.readUnlock(); } + private void markPositionOnScrollbar(int position, Color color) { + try { + scrollbarOverlay.addMarker(ActionUtils.getLineNumber(pane, position), color); + } catch (BadLocationException ex) { + //ignore + } + } + @Override public void config(Configuration config) { Color markerColor = config.getColor(PROPERTY_COLOR, DEFAULT_COLOR); Color errorColor = config.getColor(PROPERTY_ERRORCOLOR, DEFAULT_ERRORCOLOR); this.marker = new Markers.SimpleMarker(markerColor); - this.errorMarker = new WavyUnderLinePainter(Color.red); //Markers.SimpleMarker(errorColor); + this.errorMarker = new WavyUnderLinePainter(errorColor); //Markers.SimpleMarker(errorColor); String types = config.getString( PROPERTY_TOKENTYPES, DEFAULT_TOKENTYPES); @@ -236,8 +276,6 @@ public class ActionVariableMarker implements SyntaxComponent, CaretListener, Pro if (editor instanceof LineMarkedEditorPane) { ((LineMarkedEditorPane) editor).setLinkHandler(this); } - documentUpdated(); - markTokenAt(editor.getCaretPosition()); mouseMotionAdapter = new MouseMotionAdapter() { @Override public void mouseMoved(MouseEvent e) { @@ -245,6 +283,28 @@ public class ActionVariableMarker implements SyntaxComponent, CaretListener, Pro } }; editor.addMouseMotionListener(mouseMotionAdapter); + + JScrollPane scrollPane = (JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, editor); + + originalScrollPaneUI = scrollPane.getUI(); + scrollPane.setUI(new BasicScrollPaneUI()); + + JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar(); + originalScrollBarUI = verticalScrollBar.getUI(); + if (originalScrollBarUI instanceof SubstanceScrollBarUI) { + verticalScrollBar.setUI(new TrackRectSubstanceScrollbarUI(verticalScrollBar)); + } + + scrollbarOverlay = new ScrollbarOverlay((LineMarkedEditorPane) pane); + + layer = new JLayer<>(verticalScrollBar, scrollbarOverlay); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + + JPanel panel = (JPanel) SwingUtilities.getAncestorOfClass(JPanel.class, scrollPane); + panel.add(layer, BorderLayout.EAST); + + documentUpdated(); + markTokenAt(editor.getCaretPosition()); status = Status.INSTALLING; } @@ -278,6 +338,14 @@ public class ActionVariableMarker implements SyntaxComponent, CaretListener, Pro if (editor instanceof LineMarkedEditorPane) { ((LineMarkedEditorPane) editor).setLinkHandler(null); } + JScrollPane scrollPane = (JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, editor); + JPanel panel = (JPanel) SwingUtilities.getAncestorOfClass(JPanel.class, scrollPane); + panel.remove(layer); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + panel.revalidate(); + panel.repaint(); + scrollPane.setUI(originalScrollPaneUI); + scrollPane.getVerticalScrollBar().setUI(originalScrollBarUI); } private static final Logger LOG = Logger.getLogger(ActionVariableMarker.class.getName()); @@ -299,6 +367,7 @@ public class ActionVariableMarker implements SyntaxComponent, CaretListener, Pro private void documentUpdated() { errors.clear(); removeErrorMarkers(); + layer.repaint(); try { SyntaxDocument sDoc = (SyntaxDocument) pane.getDocument(); diff --git a/src/com/jpexs/decompiler/flash/gui/editor/ScrollbarOverlay.java b/src/com/jpexs/decompiler/flash/gui/editor/ScrollbarOverlay.java new file mode 100644 index 000000000..3de5a011e --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/editor/ScrollbarOverlay.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2025 JPEXS + * + * 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.editor; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import javax.swing.JComponent; +import javax.swing.JLayer; +import javax.swing.JScrollBar; +import javax.swing.plaf.LayerUI; +import javax.swing.plaf.ScrollBarUI; +import jsyntaxpane.actions.ActionUtils; + +/** + * + * @author JPEXS + */ +public class ScrollbarOverlay extends LayerUI { + + private final LineMarkedEditorPane editorPane; + private final Map markedLines = new LinkedHashMap<>(); + + public ScrollbarOverlay(LineMarkedEditorPane editorPane) { + this.editorPane = editorPane; + } + + public void removeMarkers(Color color) { + Set lines = new HashSet<>(markedLines.keySet()); + for (int key : lines) { + if (markedLines.get(key).equals(color)) { + markedLines.remove(key); + } + } + } + + public void clearMarkers() { + markedLines.clear(); + } + + public void addMarker(int line, Color color) { + markedLines.put(line, color); + } + + public void setMarkedLines(Map markedLines) { + this.markedLines.clear(); + this.markedLines.putAll(markedLines); + } + + @Override + public void paint(Graphics g, JComponent c) { + super.paint(g, c); + JScrollBar bar = (JScrollBar) ((JLayer) c).getView(); + ScrollBarUI ui = bar.getUI(); + Rectangle r; + if (ui instanceof TrackRectSubstanceScrollbarUI) { + r = ((TrackRectSubstanceScrollbarUI) ui).getTrackBounds(); + } else { + r = new Rectangle(0, 16, 16, bar.getHeight() - 32); + } + + int totalLineCount = ActionUtils.getLineCount(editorPane); + for (Map.Entry entry : markedLines.entrySet()) { + g.setColor(entry.getValue()); + + float ratio = (float) entry.getKey() / totalLineCount; + int y = r.y + (int) (ratio * r.height); + g.drawLine(0, y, bar.getWidth(), y); + } + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/editor/TrackRectSubstanceScrollbarUI.java b/src/com/jpexs/decompiler/flash/gui/editor/TrackRectSubstanceScrollbarUI.java new file mode 100644 index 000000000..e4ac53e9e --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/editor/TrackRectSubstanceScrollbarUI.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 JPEXS + * + * 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.editor; + +import java.awt.Rectangle; +import javax.swing.JComponent; +import org.pushingpixels.substance.internal.ui.SubstanceScrollBarUI; + +/** + * + * @author JPEXS + */ +public class TrackRectSubstanceScrollbarUI extends SubstanceScrollBarUI { + public TrackRectSubstanceScrollbarUI(JComponent b) { + super(b); + } + + @Override + public Rectangle getTrackBounds() { + return super.getTrackBounds(); + } +}