diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bd3a1e6c..ddfb5b35a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added +- Allowed copy/cut tags to clipboard across multiple SWFs +- Keyboard shortcuts for tag clipboard operations +- Hilight clipboard panel on copy/cut action for a few seconds ## [16.2.0] - 2022-11-08 ### Added diff --git a/src/com/jpexs/decompiler/flash/gui/ClipboardPanel.java b/src/com/jpexs/decompiler/flash/gui/ClipboardPanel.java index e905fc54f..9c3280b2a 100644 --- a/src/com/jpexs/decompiler/flash/gui/ClipboardPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ClipboardPanel.java @@ -21,6 +21,7 @@ import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Cursor; import java.awt.FlowLayout; +import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; @@ -32,6 +33,8 @@ import java.awt.event.MouseEvent; import java.awt.geom.GeneralPath; import java.util.EnumSet; import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; @@ -63,6 +66,8 @@ public class ClipboardPanel extends JPanel { private JLabel label; private MainPanel mainPanel; + + private Timer timer = null; public ClipboardPanel(MainPanel mainPanel) { this.mainPanel = mainPanel; @@ -140,4 +145,20 @@ public class ClipboardPanel extends JPanel { } setVisible(clipboardSize > 0); } + + public void flash() { + if (timer != null) { + timer.cancel(); + } + label.setFont(label.getFont().deriveFont(Font.BOLD)); + repaint(); + + timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + label.setFont(label.getFont().deriveFont(Font.PLAIN)); + } + }, 1000); + } } diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index a7f10138c..2e10a388e 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -399,6 +399,65 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se private boolean clipboardCut = false; + + private void handleTreeKeyPressed(KeyEvent e) { + AbstractTagTree tree = (AbstractTagTree)e.getSource(); + if ((e.getKeyCode() == 'C' || e.getKeyCode() == 'X') && (e.isControlDown())) { + TreePath[] paths = tree.getSelectionPaths(); + if (paths == null || paths.length == 0) { + return; + } + + List tagItems = new ArrayList<>(); + for (TreePath treePath : paths) { + TreeItem item = (TreeItem) treePath.getLastPathComponent(); + if (item instanceof TagScript) { + tagItems.add(((TagScript) item).getTag()); + } else if (item instanceof Tag) { + tagItems.add((Tag) item); + } + } + if (e.getKeyCode() == 'C') { + if (e.isShiftDown()) { + contextPopupMenu.copyTagToClipboardWithDependenciesActionPerformed(null, tagItems); + } else { + contextPopupMenu.copyTagToClipboardActionPerformed(null, tagItems); + } + } + if (e.getKeyCode() == 'X') { + if (e.isShiftDown()) { + contextPopupMenu.cutTagToClipboardWithDependenciesActionPerformed(null); + } else { + contextPopupMenu.cutTagToClipboardActionPerformed(null); + } + } + repaintTree(); + } + if (e.getKeyCode() == 'V' && e.isControlDown()) { + TreePath[] paths = tree.getSelectionPaths(); + if (paths == null || paths.length == 0) { + return; + } + + List items = new ArrayList<>(); + for (TreePath treePath : paths) { + TreeItem item = (TreeItem) treePath.getLastPathComponent(); + items.add(item); + } + if (items.size() > 1) { + return; + } + TreeItem firstItem = items.get(0); + if (!((firstItem instanceof Tag) || (firstItem instanceof Frame))) { + return; + } + if (e.isShiftDown()) { + contextPopupMenu.pasteAfterActionPerformed(null); + } else { + contextPopupMenu.pasteBeforeActionPerformed(null); + } + } + } public void gcClipboard() { for (int i = orderedClipboard.size() - 1; i >= 0; i--) { WeakReference ref = orderedClipboard.get(i); @@ -428,6 +487,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se clipboardCut = false; resourcesClipboardPanel.update(); tagListClipboardPanel.update(); + resourcesClipboardPanel.flash(); + tagListClipboardPanel.flash(); } public void cutToClipboard(Collection items) { @@ -888,6 +949,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se searchPanel.setVisible(true); filterField.requestFocusInWindow(); } + handleTreeKeyPressed(e); if ((e.getKeyCode() == 'G') && (e.isControlDown())) { SWF swf = getCurrentSwf(); if (swf != null) { @@ -921,6 +983,12 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } } }); + tagListTree.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + handleTreeKeyPressed(e); + } + }); detailPanel.setVisible(false); updateUi(); 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 a74ee636e..17c17d4cb 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -535,7 +535,7 @@ preview.gotoframe.dialog.frame.error = Neplatn\u00e9 \u010d\u00edslo sn\u00edmku error.text.invalid.continue = Neplatn\u00fd text: %text% na \u0159\u00e1dku %line%. Chcete pokra\u010dovat? #after version 4.0.5 -contextmenu.copyTag = Zkop\u00edrovat tag do +contextmenu.copyTag = Kop\u00edrovat tag do fit = p\u0159izp\u016fsobit button.setAdvanceValues = Nastavit hodnoty advance @@ -575,7 +575,7 @@ text.toggleCase = P\u0159epnout mal\u00e1/velk\u00e1 #after version 5.0.2 preview.loop = Opakovat menu.file.import.script = Importovat skripty -contextmenu.copyTagWithDependencies = Kop\u00edrovat tagy se z\u00e1vislostmi do +contextmenu.copyTagWithDependencies = Kop\u00edrovat tag se z\u00e1vislostmi do button.replaceWithTag = Nahradit jin\u00fdm charakterov\u00fdm tagem button.resolveConstants = Resolvovat konstanty diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index 36c7eddce..239d09eff 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -145,15 +145,14 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem exportSwfXmlMenuItem; private JMenuItem importSwfXmlMenuItem; - - private JMenuItem importScriptsMenuItem; - - private JMenuItem importTextsMenuItem; - - private JMenuItem importImagesMenuItem; - - private JMenuItem importSymbolClassMenuItem; + private JMenuItem importScriptsMenuItem; + + private JMenuItem importTextsMenuItem; + + private JMenuItem importImagesMenuItem; + + private JMenuItem importSymbolClassMenuItem; private JMenuItem closeMenuItem; @@ -285,22 +284,22 @@ public class TagTreeContextMenu extends JPopupMenu { importSwfXmlMenuItem.addActionListener(mainPanel::importSwfXmlActionPerformed); importSwfXmlMenuItem.setIcon(View.getIcon("importxml16")); add(importSwfXmlMenuItem); - + importScriptsMenuItem = new JMenuItem(mainPanel.translate("menu.file.import.script")); importScriptsMenuItem.addActionListener(this::importScriptsActionPerformed); importScriptsMenuItem.setIcon(View.getIcon("importscript16")); add(importScriptsMenuItem); - + importTextsMenuItem = new JMenuItem(mainPanel.translate("menu.file.import.text")); importTextsMenuItem.addActionListener(this::importTextsActionPerformed); importTextsMenuItem.setIcon(View.getIcon("importtext16")); add(importTextsMenuItem); - + importImagesMenuItem = new JMenuItem(mainPanel.translate("menu.file.import.image")); importImagesMenuItem.addActionListener(this::importImagesActionPerformed); importImagesMenuItem.setIcon(View.getIcon("importimage16")); add(importImagesMenuItem); - + importSymbolClassMenuItem = new JMenuItem(mainPanel.translate("menu.file.import.symbolClass")); importSymbolClassMenuItem.addActionListener(this::importSymbolClassActionPerformed); importSymbolClassMenuItem.setIcon(View.getIcon("importsymbolclass16")); @@ -359,22 +358,22 @@ public class TagTreeContextMenu extends JPopupMenu { copyTagToWithDependenciesMenu.setIcon(View.getIcon("copy16")); add(copyTagToWithDependenciesMenu); - cutTagToClipboardMenuItem = new JMenuItem(mainPanel.translate("contextmenu.cutTag")); - cutTagToClipboardMenuItem.setIcon(View.getIcon("cut16")); + cutTagToClipboardMenuItem = new JMenuItem(mainPanel.translate("contextmenu.cutTag") + " (CTRL+X)"); + cutTagToClipboardMenuItem.setIcon(View.getIcon("cut16")); cutTagToClipboardMenuItem.addActionListener(this::cutTagToClipboardActionPerformed); add(cutTagToClipboardMenuItem); - cutTagToClipboardWithDependenciesMenuItem = new JMenuItem(mainPanel.translate("contextmenu.cutTagWithDependencies")); + cutTagToClipboardWithDependenciesMenuItem = new JMenuItem(mainPanel.translate("contextmenu.cutTagWithDependencies") + " (CTRL+SHIFT+X)"); cutTagToClipboardWithDependenciesMenuItem.setIcon(View.getIcon("cut16")); cutTagToClipboardWithDependenciesMenuItem.addActionListener(this::cutTagToClipboardWithDependenciesActionPerformed); add(cutTagToClipboardWithDependenciesMenuItem); - pasteBeforeMenuItem = new JMenuItem(mainPanel.translate("contextmenu.pasteBefore")); + pasteBeforeMenuItem = new JMenuItem(mainPanel.translate("contextmenu.pasteBefore") + " (CTRL+V)"); pasteBeforeMenuItem.setIcon(View.getIcon("paste16")); pasteBeforeMenuItem.addActionListener(this::pasteBeforeActionPerformed); add(pasteBeforeMenuItem); - pasteAfterMenuItem = new JMenuItem(mainPanel.translate("contextmenu.pasteAfter")); + pasteAfterMenuItem = new JMenuItem(mainPanel.translate("contextmenu.pasteAfter") + " (CTRL+SHIFT+V)"); pasteAfterMenuItem.setIcon(View.getIcon("paste16")); pasteAfterMenuItem.addActionListener(this::pasteAfterActionPerformed); add(pasteAfterMenuItem); @@ -652,13 +651,13 @@ public class TagTreeContextMenu extends JPopupMenu { jumpToCharacterMenuItem.setVisible(false); exportJavaSourceMenuItem.setVisible(allSelectedIsSwf); exportSwfXmlMenuItem.setVisible(allSelectedIsSwf); - + importImagesMenuItem.setVisible(false); importScriptsMenuItem.setVisible(false); importSymbolClassMenuItem.setVisible(false); importTextsMenuItem.setVisible(false); importSwfXmlMenuItem.setVisible(false); - + closeMenuItem.setVisible(allSelectedIsSwf); addTagInsideMenu.setVisible(false); attachTagMenu.setVisible(false); @@ -828,14 +827,14 @@ public class TagTreeContextMenu extends JPopupMenu { if (firstItem instanceof Tag) { showInHexDumpViewTagMenuItem.setVisible(true); } - + if (firstItem instanceof SWF) { importImagesMenuItem.setVisible(true); importScriptsMenuItem.setVisible(true); importSymbolClassMenuItem.setVisible(true); importTextsMenuItem.setVisible(true); importSwfXmlMenuItem.setVisible(true); - } + } if (!mainPanel.clipboardEmpty()) { if ((firstItem instanceof SWF) || (firstItem instanceof DefineSpriteTag) || (firstItem instanceof Frame)) { @@ -848,36 +847,46 @@ public class TagTreeContextMenu extends JPopupMenu { } } - if (allSelectedIsInTheSameSwf && allSelectedIsTag && swfs.size() > 1) { - moveTagToMenu.removeAll(); - copyTagToMenu.removeAll(); - copyTagToWithDependenciesMenu.removeAll(); + moveTagToMenu.removeAll(); + copyTagToMenu.removeAll(); + copyTagToWithDependenciesMenu.removeAll(); - if (allSelectedSameParent) { - JMenuItem copyToClipboardMenuItem = new JMenuItem(AppStrings.translate("contextmenu.clipboard"), View.getIcon("clipboard16")); - copyToClipboardMenuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - copyTagToClipboardActionPerformed(e, items); - } - }); - - copyTagToMenu.add(copyToClipboardMenuItem); - - JMenuItem copyToClipboardWithDependenciesMenuItem = new JMenuItem(AppStrings.translate("contextmenu.clipboard"), View.getIcon("clipboard16")); - copyToClipboardWithDependenciesMenuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - copyTagToClipboardWithDependenciesActionPerformed(e, items); - } - }); - - copyTagToWithDependenciesMenu.add(copyToClipboardWithDependenciesMenuItem); - - cutTagToClipboardMenuItem.setVisible(true); - cutTagToClipboardWithDependenciesMenuItem.setVisible(true); + if (allSelectedIsTag) { + List tagItems = new ArrayList<>(); + for(TreeItem item:items) { + if (item instanceof TagScript) { + tagItems.add(((TagScript)item).getTag()); + } else { + tagItems.add((Tag) item); + } } + JMenuItem copyToClipboardMenuItem = new JMenuItem(AppStrings.translate("contextmenu.clipboard") + " (CTRL+C)", View.getIcon("clipboard16")); + copyToClipboardMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + copyTagToClipboardActionPerformed(e, tagItems); + } + }); + copyTagToMenu.add(copyToClipboardMenuItem); + + JMenuItem copyToClipboardWithDependenciesMenuItem = new JMenuItem(AppStrings.translate("contextmenu.clipboard") + " (CTRL+SHIFT+C)", View.getIcon("clipboard16")); + copyToClipboardWithDependenciesMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + copyTagToClipboardWithDependenciesActionPerformed(e, tagItems); + } + }); + + copyTagToWithDependenciesMenu.add(copyToClipboardWithDependenciesMenuItem); + + cutTagToClipboardMenuItem.setVisible(true); + cutTagToClipboardWithDependenciesMenuItem.setVisible(true); + + copyTagToMenu.setVisible(true); + copyTagToWithDependenciesMenu.setVisible(true); + } + if (allSelectedIsInTheSameSwf && allSelectedIsTag && swfs.size() > 1) { for (SWFList targetSwfList : swfs) { if ((targetSwfList.size() == 1) && (targetSwfList.get(0) == singleSwf)) { continue; @@ -904,7 +913,7 @@ public class TagTreeContextMenu extends JPopupMenu { openSWFInsideTagMenuItem.setVisible(anyInnerSwf); } - + for (TreeItem item : items) { if (item instanceof Tag) { if (((Tag) item).isReadOnly()) { @@ -1277,7 +1286,7 @@ public class TagTreeContextMenu extends JPopupMenu { sourceSwf.removeTag(tag); tag.setSwf(targetSwf, true); checkUniqueCharacterId(tag); - targetSwf.addTag(tag); + targetSwf.addTag(tag); targetSwf.updateCharacters(); tag.setModified(true); } @@ -1360,8 +1369,8 @@ public class TagTreeContextMenu extends JPopupMenu { int oldCharacterId = neededTag.getCharacterId(); int newCharacterId = checkUniqueCharacterId(copyTag); changedCharacterIds.put(oldCharacterId, newCharacterId); - - targetSwf.addTag(copyTag); + + targetSwf.addTag(copyTag); targetSwf.updateCharacters(); targetSwf.getCharacters(); // force rebuild character id cache @@ -1380,7 +1389,7 @@ public class TagTreeContextMenu extends JPopupMenu { int newCharacterId = checkUniqueCharacterId(copyTag); changedCharacterIds.put(oldCharacterId, newCharacterId); } - targetSwf.addTag(copyTag); + targetSwf.addTag(copyTag); targetSwf.updateCharacters(); targetSwf.getCharacters(); // force rebuild character id cache @@ -2363,7 +2372,7 @@ public class TagTreeContextMenu extends JPopupMenu { copyTag.setTimelined(timelined); checkUniqueCharacterId(copyTag); - timelined.addTag(idx + 1, copyTag); + timelined.addTag(idx + 1, copyTag); copyTag.setModified(true); timelined.resetTimeline(); @@ -2391,9 +2400,9 @@ public class TagTreeContextMenu extends JPopupMenu { copyTag.setTimelined(timelined); checkUniqueCharacterId(copyTag); - + timelined.addTag(++i, copyTag); - + copyTag.setModified(true); } @@ -2671,7 +2680,7 @@ public class TagTreeContextMenu extends JPopupMenu { } } - private void copyTagToClipboardActionPerformed(ActionEvent evt, List items) { + public void copyTagToClipboardActionPerformed(ActionEvent evt, List items) { mainPanel.copyToClipboard(items); } @@ -2700,23 +2709,23 @@ public class TagTreeContextMenu extends JPopupMenu { return clipboardItems; } - private void copyTagToClipboardWithDependenciesActionPerformed(ActionEvent evt, List items) { + public void copyTagToClipboardWithDependenciesActionPerformed(ActionEvent evt, List items) { mainPanel.copyToClipboard(getClipboardDependenciesSet(items)); } - private void cutTagToClipboardActionPerformed(ActionEvent evt) { + public void cutTagToClipboardActionPerformed(ActionEvent evt) { List items = getTree().getSelected(); mainPanel.cutToClipboard(items); mainPanel.repaintTree(); } - private void cutTagToClipboardWithDependenciesActionPerformed(ActionEvent evt) { + public void cutTagToClipboardWithDependenciesActionPerformed(ActionEvent evt) { List items = getTree().getSelected(); mainPanel.cutToClipboard(getClipboardDependenciesSet(items)); mainPanel.repaintTree(); } - private void pasteBeforeActionPerformed(ActionEvent evt) { + public void pasteBeforeActionPerformed(ActionEvent evt) { TreeItem item = getTree().getCurrentTreeItem(); Timelined timelined; Tag position; @@ -2734,7 +2743,7 @@ public class TagTreeContextMenu extends JPopupMenu { pasteBeforeAfter(true, timelined, position); } - private void pasteAfterActionPerformed(ActionEvent evt) { + public void pasteAfterActionPerformed(ActionEvent evt) { TreeItem item = getTree().getCurrentTreeItem(); Timelined timelined; Tag position; @@ -2769,20 +2778,23 @@ public class TagTreeContextMenu extends JPopupMenu { private void pasteBeforeAfter(boolean before, Timelined targetTimelined, Tag position) { Set items = mainPanel.getClipboardContents(); - SWF sourceSwf = items.iterator().next().getSwf(); + Set sourceSwfs = new LinkedHashSet<>(); SWF targetSwf = (targetTimelined instanceof SWF) ? (SWF) targetTimelined : ((DefineSpriteTag) targetTimelined).getSwf(); - try { + try { List newTags = new ArrayList<>(); Map changedCharacterIds = new HashMap<>(); - for (TreeItem item : items) { - Tag tag = (Tag) item; + for (TreeItem item : items) { + Tag tag = (Tag) item; if (tag.getSwf() == null) { continue; } + SWF sourceSwf = tag.getSwf(); + sourceSwfs.add(sourceSwf); + if (mainPanel.isClipboardCut()) { tag.getTimelined().removeTag(tag); } - + Timelined realTargetTimelined = targetTimelined; Tag realPosition = position; @@ -2796,7 +2808,7 @@ public class TagTreeContextMenu extends JPopupMenu { realTargetTimelined = spriteTag.getTimelined(); //should be SWF realPosition = spriteTag; } - if (sourceSwf != targetSwf || !mainPanel.isClipboardCut()) { + if (sourceSwf != targetSwf || !mainPanel.isClipboardCut()) { ReadOnlyTagList tags = realTargetTimelined.getTags(); int positionInt = realPosition == null ? tags.size() : tags.indexOf(realPosition); @@ -2815,12 +2827,12 @@ public class TagTreeContextMenu extends JPopupMenu { targetSwf.updateCharacters(); targetSwf.getCharacters(); // force rebuild character id cache copyTag.setModified(true); - newTags.add(copyTag); + newTags.add(copyTag); } else if (sourceSwf == targetSwf && mainPanel.isClipboardCut()) { ReadOnlyTagList tags = realTargetTimelined.getTags(); int positionInt = realPosition == null ? tags.size() : tags.indexOf(realPosition); realTargetTimelined.addTag(positionInt, tag); - tag.setTimelined(realTargetTimelined); + tag.setTimelined(realTargetTimelined); } } for (int oldCharacterId : changedCharacterIds.keySet()) { @@ -2829,48 +2841,50 @@ public class TagTreeContextMenu extends JPopupMenu { // todo: avoid double replaces newTag.replaceCharacter(oldCharacterId, newCharacterId); } - } - - if (sourceSwf != targetSwf) { - sourceSwf.assignExportNamesToSymbols(); - sourceSwf.assignClassesToSymbols(); - sourceSwf.clearImageCache(); - sourceSwf.updateCharacters(); - sourceSwf.resetTimelines(sourceSwf); + } + + for (SWF sourceSwf : sourceSwfs) { + if (sourceSwf != targetSwf) { + sourceSwf.assignExportNamesToSymbols(); + sourceSwf.assignClassesToSymbols(); + sourceSwf.clearImageCache(); + sourceSwf.updateCharacters(); + sourceSwf.resetTimelines(sourceSwf); + } } targetSwf.assignExportNamesToSymbols(); targetSwf.assignClassesToSymbols(); targetSwf.clearImageCache(); targetSwf.updateCharacters(); targetSwf.resetTimelines(targetSwf); - + if (mainPanel.isClipboardCut()) { mainPanel.emptyClipboard(); } mainPanel.refreshTree(targetSwf); } catch (InterruptedException | IOException ex) { Logger.getLogger(TagTreeContextMenu.class.getName()).log(Level.SEVERE, null, ex); - } + } } - + public void importScriptsActionPerformed(ActionEvent evt) { SWF swf = getTree().getCurrentTreeItem().getSwf(); mainPanel.importScript(swf); } - + public void importTextsActionPerformed(ActionEvent evt) { SWF swf = getTree().getCurrentTreeItem().getSwf(); mainPanel.importText(swf); } - + public void importImagesActionPerformed(ActionEvent evt) { SWF swf = getTree().getCurrentTreeItem().getSwf(); mainPanel.importImage(swf); } - + public void importSymbolClassActionPerformed(ActionEvent evt) { SWF swf = getTree().getCurrentTreeItem().getSwf(); mainPanel.importSymbolClass(swf); } - + }