From 8d0952a8fc0db1eccb34b572046bac3d283a5df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 6 Jul 2025 15:34:45 +0200 Subject: [PATCH] Added: Copy (selectable) text to clipboard, select all No text selection in object selection mode. --- CHANGELOG.md | 2 +- .../decompiler/flash/gui/ImagePanel.java | 116 +++++++++++++++++- .../flash/gui/graphics/selectall16.png | Bin 0 -> 441 bytes .../flash/gui/locales/MainFrame.properties | 3 + .../flash/gui/locales/MainFrame_cs.properties | 5 +- 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/selectall16.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 814494aca..db63a11f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file. and shows its progress - AS2 - show deobfuscated class/package names in the class tree - Allow obfuscated DefineEditText variable identifiers -- Selectable text (DefineEditTexts with noselect=0) +- Selectable text (DefineEditTexts with noselect=0), copy to clipboard, select all - AS1/2 P-code curly braces pair highlighting ### Fixed diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index 500da72a2..070d13d5c 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -43,6 +43,7 @@ import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.DisplayObjectCacheKey; import com.jpexs.decompiler.flash.tags.base.DrawableTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.RenderContext; import com.jpexs.decompiler.flash.tags.base.SoundTag; @@ -54,6 +55,7 @@ import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.decompiler.flash.timeline.Timelined; import com.jpexs.decompiler.flash.types.BUTTONCONDACTION; import com.jpexs.decompiler.flash.types.ConstantColorColorTransform; +import com.jpexs.decompiler.flash.types.GLYPHENTRY; import com.jpexs.decompiler.flash.types.RECT; import com.jpexs.decompiler.flash.types.RGB; import com.jpexs.decompiler.flash.types.SOUNDINFO; @@ -87,6 +89,8 @@ import java.awt.Stroke; import java.awt.SystemColor; import java.awt.Toolkit; import java.awt.Transparency; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AdjustmentEvent; @@ -127,7 +131,9 @@ import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; import javax.swing.BoxLayout; import javax.swing.JLabel; +import javax.swing.JMenuItem; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JScrollBar; import javax.swing.JTextField; import javax.swing.SwingUtilities; @@ -416,6 +422,12 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private Timer textCursorBlinkTimer; + private void setTextSelection(int value) { + int selStart = getSelectionStartInt(); + int delta = value - selStart; + changeTextSelection(delta); + } + private void changeTextSelection(int delta) { TextTag text = textSelectionText; if (text == null) { @@ -1646,6 +1658,78 @@ public final class ImagePanel extends JPanel implements MediaDisplay { @Override public void mouseClicked(MouseEvent e) { + if (SwingUtilities.isRightMouseButton(e) && !selectionMode && !doFreeTransform) { + TextTag text = mouseOverText; + if (text != null) { + if (text instanceof DefineEditTextTag) { + DefineEditTextTag dtext = (DefineEditTextTag) text; + List recs = dtext.getTextRecords(dtext.getSwf()); + FontTag font = null; + int pos = 0; + int selStart = getSelectionStartInt(); + int selEnd = getSelectionEndInt(); + StringBuilder sb = new StringBuilder(); + int y = 0; + for (TEXTRECORD r : recs) { + if (r.styleFlagsHasFont) { + font = r.getFont(dtext.getSwf()); + } + if (r.styleFlagsHasYOffset) { + int oldY = y; + y = r.yOffset; + if (text == textSelectionText && pos >= selStart && pos < selEnd) { + if (y > oldY && sb.length() > 0) { + sb.append("\n"); + } + } + } + if (font == null) { + continue; + } + for (GLYPHENTRY g : r.glyphEntries) { + if (text == textSelectionText && pos >= selStart && pos < selEnd) { + sb.append(font.glyphToChar(g.glyphIndex)); + } + pos++; + } + } + String textToCopy = sb.toString(); + int fullLength = pos; + + JPopupMenu pm = new JPopupMenu(); + JMenuItem copyMenuItem = new JMenuItem(AppStrings.translate("text.copy"), View.getIcon("copy16")); + copyMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + StringSelection stringSelection = new StringSelection(textToCopy); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(stringSelection, null); + } + }); + JMenuItem selectAllMenuItem = new JMenuItem(AppStrings.translate("text.selectAll"), View.getIcon("selectall16")); + selectAllMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + textSelectionText = text; + textSelectionStartPrecise = 0.0; + textSelectionEndPrecise = (double) fullLength; + textSelectionStartGlyphRect = new Rectangle2D.Double(); //?? + textSelectionStartGlyphXPosition = 0.0; //?? + + repaint(); + } + }); + + if (!textToCopy.isEmpty()) { + pm.add(copyMenuItem); + } + pm.add(selectAllMenuItem); + pm.show(iconPanel, e.getX(), e.getY()); + + } + } + } + if (e.getClickCount() == 2) { if (Configuration.showGuides.get() && !Configuration.lockGuides.get()) { Point mousePoint = e.getPoint(); @@ -4217,7 +4301,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { if (text == null || (!allowSelectAllTextTypes && (!(text instanceof DefineEditTextTag) || ((DefineEditTextTag) text).noSelect))) { text = null; } - if (text != null && !doFreeTransform) { + if (text != null && !doFreeTransform && !selectionMode) { Rectangle2D glyphRect = iconPanel.glyphUnderCursorRect; if (iconPanel.glyphPosUnderCursor > -1 && glyphRect != null) { if (mouseButton == 1) { @@ -4721,6 +4805,11 @@ public final class ImagePanel extends JPanel implements MediaDisplay { hilightedPoints = null; selectedDepths = new ArrayList<>(); selectedPoints = new ArrayList<>(); + textSelectionEndPrecise = null; + textSelectionStartPrecise = null; + textSelectionStartGlyphRect = null; + textSelectionStartGlyphXPosition = null; + textSelectionText = null; pointEditPanel.setVisible(false); this.showObjectsUnderCursor = showObjectsUnderCursor; this.registrationPointPosition = RegistrationPointPosition.CENTER; @@ -5455,6 +5544,29 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } return selStartInt; } + + private int getSelectionEndInt() { + Double selStart = textSelectionStartPrecise; + Double selEnd = textSelectionEndPrecise; + + if (selStart == null) { + return -1; + } + + if (selStart != null && selEnd != null) { + if (selStart > selEnd) { + double tmp = selStart; + selStart = selEnd; + selEnd = tmp; + } + } + int selEndInt = (int) Math.floor(selEnd); + double endFract = selEnd - selEndInt; + if (endFract > 0.7) { + selEndInt++; + } + return selEndInt; + } private void drawFrame(Timer thisTimer, boolean display) { Timelined timelined; @@ -5779,7 +5891,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } } else if (handCursor) { newCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); - } else if (selectingText || (iconPanel.mouseOverText != null && (allowSelectAllTextTypes || (iconPanel.mouseOverText instanceof DefineEditTextTag && !((DefineEditTextTag) iconPanel.mouseOverText).noSelect)))) { + } else if (!selectionMode && !doFreeTransform && (selectingText || (iconPanel.mouseOverText != null && (allowSelectAllTextTypes || (iconPanel.mouseOverText instanceof DefineEditTextTag && !((DefineEditTextTag) iconPanel.mouseOverText).noSelect))))) { newCursor = textCursor; } else if (zoomAvailable && iconPanel.hasAllowMove()) { newCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/selectall16.png b/src/com/jpexs/decompiler/flash/gui/graphics/selectall16.png new file mode 100644 index 0000000000000000000000000000000000000000..f4b0b19e0911c64b51a27f62439117b771c89a2d GIT binary patch literal 441 zcmV;q0Y?6bP)5 zlRZwuKoEuBShkdo6Ce-`g&oB%5D1AAAQ1N;1q8tjkfK1KNiIPN7oddNiR2Ov5h<~E z_fdp3{)rVb&8)pM-@G@{3Q0UZ4Mtk4OKcFX>yB@(@46s~{@~tdt!a#*F@{=eYOSf1 zqEd=kmNCmRrfJGFO}SVs_%Q4nl5l^40st@X2}ET1qmqkpcDR%GSO7!>B7!7IO2xkK zV>+FdzDri@f#k|*B7z_Yns?E7Q-Rnx+21PNIsZ`*$1&nK&TTd-5E1ykpBLKVPN(xX z(Q%pq7E)}k=0*TX0KjB2soAg*MG+jw$!k|yXt&!X1sBJAc>Vao`Oz-kKEKy1r~+;C zY&KiDbCm>MlzSK^K=cW00000NkvXXu0mjf50ACM literal 0 HcmV?d00001 diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 56226de1c..73745376b 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1088,3 +1088,6 @@ abc.link.dialog.noOtherFilesOpened = No other files opened #after 24.0.1 deobfuscate_options.skip_uninitialized_class_fields_detection = Skip detection of uninitialized class fields work.decompiling.allScripts.ucf.canBeSkipped = You can turn off this step in the deobfuscation options (icon with the red cross) + +text.copy = Copy +text.selectAll = Select all diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index 3929eeb3b..f266957db 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -1085,4 +1085,7 @@ transform.clipboard.paste.apply = Pou\u017e\u00edt transformaci ze schr\u00e1nky abc.link.dialog.noOtherFilesOpened = Nejsou otev\u0159eny \u017e\u00e1dn\u00e9 jin\u00e9 soubory #after 24.0.1 -deobfuscate_options.skip_uninitialized_class_fields_detection = P\u0159esko\u010dit detekci neinicializovan\u00fdch pol\u00ed t\u0159\u00edd \ No newline at end of file +deobfuscate_options.skip_uninitialized_class_fields_detection = P\u0159esko\u010dit detekci neinicializovan\u00fdch pol\u00ed t\u0159\u00edd + +text.copy = Kop\u00edrovat +text.selectAll = Vybrat v\u0161e