diff --git a/CHANGELOG.md b/CHANGELOG.md index b8ed6f4e8..8a5b40472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ All notable changes to this project will be documented in this file. - Move tag with dependencies - Copy/Move tag operation has select position dialog - Select position dialog has target file in its title +- [#1649] Moving SWF files (and bundles) up and down (comtext menuitem + ALT up/down shortcut) +- Moving tags up and down (context menuitem + ALT up/down shortcut) ### Fixed - Exception when bundle selected @@ -2538,6 +2540,7 @@ All notable changes to this project will be documented in this file. [alpha 9]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha8...alpha9 [alpha 8]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha7...alpha8 [alpha 7]: https://github.com/jindrapetrik/jpexs-decompiler/releases/tag/alpha7 +[#1649]: https://www.free-decompiler.com/flash/issues/1649 [#1863]: https://www.free-decompiler.com/flash/issues/1863 [#1414]: https://www.free-decompiler.com/flash/issues/1414 [#1755]: https://www.free-decompiler.com/flash/issues/1755 diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 44a635588..65a767f1a 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -399,6 +399,63 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se private boolean clipboardCut = false; + private void handleTreeKeyReleased(KeyEvent e) { + AbstractTagTree tree = (AbstractTagTree) e.getSource(); + if ((e.getKeyCode() == KeyEvent.VK_UP + || e.getKeyCode() == KeyEvent.VK_DOWN) + && e.isAltDown() && !e.isControlDown() && !e.isShiftDown()) { + TreePath paths[] = tree.getSelectionPaths(); + if (paths == null || paths.length != 1) { + return; + } + TreeItem item = (TreeItem) paths[0].getLastPathComponent(); + if (e.getKeyCode() == KeyEvent.VK_UP) { + contextPopupMenu.moveUpDown(item, true); + } + if (e.getKeyCode() == KeyEvent.VK_DOWN) { + contextPopupMenu.moveUpDown(item, false); + } + } + } + + public void moveSwfListUpDown(TreeItem item, boolean up) { + SWFList swfList = null; + if (item instanceof SWF) { + SWF swf = (SWF) item; + if (swf.swfList != null && !swf.swfList.isBundle() && swf.swfList.size() == 1) { + swfList = swf.swfList; + } else { + return; + } + } else if (item instanceof SWFList) { + swfList = (SWFList) item; + } else { + return; + } + int index = swfs.indexOf(swfList); + + List> expandedTagTree = View.getExpandedNodes(tagTree); + List> expandedTagListTree = View.getExpandedNodes(tagListTree); + + if (up) { + if (index <= 0) { + return; + } + swfs.move(index, index - 1); + } else { + if (index < 0 || index >= swfs.size() - 1) { + return; + } + swfs.move(index, index + 2); + } + View.expandTreeNodes(tagTree, expandedTagTree); + View.expandTreeNodes(tagListTree, expandedTagListTree); + TreePath path = getCurrentTree().getModel().getTreePath(item); + getCurrentTree().setSelectionPath(path); + getCurrentTree().scrollPathToVisible(path); + repaintTree(); + } + private void handleTreeKeyPressed(KeyEvent e) { AbstractTagTree tree = (AbstractTagTree) e.getSource(); if ((e.getKeyCode() == 'C' || e.getKeyCode() == 'X') && (e.isControlDown())) { @@ -943,6 +1000,11 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se showContentPanelCard(WELCOME_PANEL); tagTree.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + handleTreeKeyReleased(e); + } + @Override public void keyPressed(KeyEvent e) { if ((e.getKeyCode() == 'F') && (e.isControlDown())) { @@ -984,6 +1046,11 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } }); tagListTree.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + handleTreeKeyReleased(e); + } + @Override public void keyPressed(KeyEvent e) { handleTreeKeyPressed(e); diff --git a/src/com/jpexs/decompiler/flash/gui/helpers/CollectionChangedAction.java b/src/com/jpexs/decompiler/flash/gui/helpers/CollectionChangedAction.java index ef0348992..bf8c48bd9 100644 --- a/src/com/jpexs/decompiler/flash/gui/helpers/CollectionChangedAction.java +++ b/src/com/jpexs/decompiler/flash/gui/helpers/CollectionChangedAction.java @@ -22,5 +22,5 @@ package com.jpexs.decompiler.flash.gui.helpers; */ public enum CollectionChangedAction { - ADD, REMOVE, RESET + ADD, REMOVE, RESET, MOVE } diff --git a/src/com/jpexs/decompiler/flash/gui/helpers/CollectionChangedEvent.java b/src/com/jpexs/decompiler/flash/gui/helpers/CollectionChangedEvent.java index fafe64d96..de7c36318 100644 --- a/src/com/jpexs/decompiler/flash/gui/helpers/CollectionChangedEvent.java +++ b/src/com/jpexs/decompiler/flash/gui/helpers/CollectionChangedEvent.java @@ -49,6 +49,14 @@ public class CollectionChangedEvent { break; } } + + public CollectionChangedEvent(CollectionChangedAction action, E oldItem, E newItem, int oldIndex, int newIndex) { + this.action = action; + this.oldItem = oldItem; + this.newItem = newItem; + this.oldIndex = oldIndex; + this.newIndex = newIndex; + } public CollectionChangedAction getAction() { return action; diff --git a/src/com/jpexs/decompiler/flash/gui/helpers/ObservableList.java b/src/com/jpexs/decompiler/flash/gui/helpers/ObservableList.java index 8463f36fb..b84dde7c2 100644 --- a/src/com/jpexs/decompiler/flash/gui/helpers/ObservableList.java +++ b/src/com/jpexs/decompiler/flash/gui/helpers/ObservableList.java @@ -79,6 +79,55 @@ public class ObservableList implements List { return false; } + + /** * Move item to desired position.0 A + 1 B + 2 C + 3 D + 4 E + + + move(1, 3) + + 0 A + 1 C + 2 B + 3 D + 4 E + + move(3, 1) + 0 A + 1 D + 2 B + 3 C + 4 E + * @param oldIndex + * @param newIndex + * @return + */ + public boolean move(int oldIndex, int newIndex) { + if (oldIndex == newIndex) { + return true; + } + if (oldIndex < 0 || oldIndex >= size()) { + throw new ArrayIndexOutOfBoundsException(oldIndex); + } + if (newIndex < 0 || newIndex > size()) { + throw new ArrayIndexOutOfBoundsException(newIndex); + } + E item = list.remove(oldIndex); + if (newIndex > oldIndex) { + list.add(newIndex - 1, item); + } else { + list.add(newIndex, item); + } + fireCollectionChanged(new CollectionChangedEvent<>(CollectionChangedAction.MOVE, item, item, oldIndex, newIndex)); + return true; + } + + public boolean move(E item, int newIndex) { + return move(indexOf(item), newIndex); + } @Override public boolean containsAll(Collection c) { diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index eb749348e..6fc83a0bd 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -945,4 +945,6 @@ clipboard.items = %count% items clipboard.clear = Clear the tag clipboard #after 16.2.0 -contextmenu.moveTagWithDependencies = Move tag with dependencies to \ No newline at end of file +contextmenu.moveTagWithDependencies = Move tag with dependencies to +contextmenu.moveUp = Move up +contextmenu.moveDown = Move down \ No newline at end of file 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 cfcf0589d..f496536d7 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -917,4 +917,6 @@ clipboard.items = %count% polo\u017eek clipboard.clear = Vy\u010distit tagovou schr\u00e1nku #after 16.2.0 -contextmenu.moveTagWithDependencies = P\u0159esunout tag se z\u00e1vislostmi do \ No newline at end of file +contextmenu.moveTagWithDependencies = P\u0159esunout tag se z\u00e1vislostmi do +contextmenu.moveUp = Posunout nahoru +contextmenu.moveDown = Posunout dol\u016f \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeModel.java b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeModel.java index 4a380628f..ea24c60fa 100644 --- a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeModel.java +++ b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeModel.java @@ -175,7 +175,8 @@ public class TagListTreeModel extends AbstractTagTreeModel { @Override public void updateSwfs(CollectionChangedEvent e) { - if (e.getAction() != CollectionChangedAction.ADD) { + if (e.getAction() != CollectionChangedAction.ADD && + e.getAction() != CollectionChangedAction.MOVE) { List toRemove = new ArrayList<>(); for (SWF swf : swfHeaders.keySet()) { SWF swf2 = swf.getRootSwf(); @@ -200,6 +201,12 @@ public class TagListTreeModel extends AbstractTagTreeModel { fireTreeNodesRemoved(new TreeModelEvent(this, rootPath, new int[]{e.getOldIndex()}, new Object[]{e.getOldItem()})); break; } + /*case MOVE: { + TreePath rootPath = new TreePath(new Object[]{root}); + fireTreeNodesRemoved(new TreeModelEvent(this, rootPath, new int[]{e.getOldIndex()}, new Object[]{e.getOldItem()})); + fireTreeNodesInserted(new TreeModelEvent(this, rootPath, new int[]{e.getNewIndex()}, new Object[]{e.getNewItem()})); + break; + }*/ default: fireTreeStructureChanged(new TreeModelEvent(this, new TreePath(root))); } diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index 9bf7bf0bf..dae84cfff 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -169,7 +169,11 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenu moveTagToMenu; private JMenu moveTagToWithDependenciesMenu; - + + private JMenuItem moveUpMenuItem; + + private JMenuItem moveDownMenuItem; + private JMenu copyTagToMenu; private JMenu copyTagToWithDependenciesMenu; @@ -207,7 +211,7 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem addFramesAfterMenuItem; private static final int KIND_MOVETO = 0; - private static final int KIND_MOVETODEPS = 1; + private static final int KIND_MOVETODEPS = 1; private static final int KIND_COPYTO = 2; private static final int KIND_COPYTODEPS = 3; @@ -357,7 +361,16 @@ public class TagTreeContextMenu extends JPopupMenu { moveTagToWithDependenciesMenu.setIcon(View.getIcon("move16")); add(moveTagToWithDependenciesMenu); - + moveUpMenuItem = new JMenuItem(mainPanel.translate("contextmenu.moveUp")+" (ALT + UP)"); + moveUpMenuItem.setIcon(View.getIcon("arrowup16")); + moveUpMenuItem.addActionListener(this::moveUpActionPerformed); + add(moveUpMenuItem); + + moveDownMenuItem = new JMenuItem(mainPanel.translate("contextmenu.moveDown") + " (ALT + DOWN)"); + moveDownMenuItem.setIcon(View.getIcon("arrowdown16")); + moveDownMenuItem.addActionListener(this::moveDownActionPerformed); + add(moveDownMenuItem); + copyTagToMenu = new JMenu(mainPanel.translate("contextmenu.copyTag")); copyTagToMenu.setIcon(View.getIcon("copy16")); add(copyTagToMenu); @@ -673,6 +686,8 @@ public class TagTreeContextMenu extends JPopupMenu { addTagAfterMenu.setVisible(false); moveTagToMenu.setVisible(false); moveTagToWithDependenciesMenu.setVisible(false); + moveUpMenuItem.setVisible(false); + moveDownMenuItem.setVisible(false); copyTagToMenu.setVisible(false); copyTagToWithDependenciesMenu.setVisible(false); cutTagToClipboardMenuItem.setVisible(false); @@ -854,6 +869,22 @@ public class TagTreeContextMenu extends JPopupMenu { pasteBeforeMenuItem.setVisible(true); } } + + if ((firstItem instanceof Tag) && (getTree() == mainPanel.tagListTree)) { + moveUpMenuItem.setVisible(true); + moveDownMenuItem.setVisible(true); + } + if (firstItem instanceof SWF) { + SWF firstSwf = (SWF) firstItem; + if (firstSwf.swfList != null && !firstSwf.swfList.isBundle() && firstSwf.swfList.size() == 1) { + moveUpMenuItem.setVisible(true); + moveDownMenuItem.setVisible(true); + } + } + if (firstItem instanceof SWFList) { + moveUpMenuItem.setVisible(true); + moveDownMenuItem.setVisible(true); + } } moveTagToMenu.removeAll(); @@ -1210,7 +1241,7 @@ public class TagTreeContextMenu extends JPopupMenu { t.setTimelined(timelined); timelined.resetTimeline(); - timelined.setFrameCount(timelined.getTimeline().getFrameCount()); + timelined.setFrameCount(timelined.getTimeline().getFrameCount()); } swf.updateCharacters(); @@ -1296,7 +1327,7 @@ public class TagTreeContextMenu extends JPopupMenu { tag.setSwf(targetSwf, true); tag.setTimelined(timelined); checkUniqueCharacterId(tag); - int positionInt = position == null ? timelined.getTags().size() : timelined.indexOfTag(position); + int positionInt = position == null ? timelined.getTags().size() : timelined.indexOfTag(position); timelined.addTag(positionInt, tag); targetSwf.updateCharacters(); tag.setModified(true); @@ -1312,9 +1343,9 @@ public class TagTreeContextMenu extends JPopupMenu { targetSwf.updateCharacters(); sourceSwf.resetTimelines(sourceSwf); targetSwf.resetTimelines(targetSwf); - + timelined.setFrameCount(timelined.getTimeline().getFrameCount()); - + mainPanel.refreshTree(new SWF[]{sourceSwf, targetSwf}); } @@ -1325,29 +1356,29 @@ public class TagTreeContextMenu extends JPopupMenu { } Tag position = selectPositionDialog.getSelectedTag(); Timelined timelined = selectPositionDialog.getSelectedTimelined(); - copyOrMoveTags(new LinkedHashSet(items), false, timelined, position); + copyOrMoveTags(new LinkedHashSet(items), false, timelined, position); } - private void copyTagWithDependenciesToActionPerformed(ActionEvent evt, List items, SWF targetSwf) { + private void copyTagWithDependenciesToActionPerformed(ActionEvent evt, List items, SWF targetSwf) { SelectTagPositionDialog selectPositionDialog = new SelectTagPositionDialog(mainPanel.getMainFrame().getWindow(), targetSwf, true); if (selectPositionDialog.showDialog() != AppDialog.OK_OPTION) { return; } Tag position = selectPositionDialog.getSelectedTag(); Timelined timelined = selectPositionDialog.getSelectedTimelined(); - - copyOrMoveTags(getDependenciesSet(items), false, timelined, position); + + copyOrMoveTags(getDependenciesSet(items), false, timelined, position); } - - private void moveTagWithDependenciesToActionPerformed(ActionEvent evt, List items, SWF targetSwf) { + + private void moveTagWithDependenciesToActionPerformed(ActionEvent evt, List items, SWF targetSwf) { SelectTagPositionDialog selectPositionDialog = new SelectTagPositionDialog(mainPanel.getMainFrame().getWindow(), targetSwf, true); if (selectPositionDialog.showDialog() != AppDialog.OK_OPTION) { return; } Tag position = selectPositionDialog.getSelectedTag(); Timelined timelined = selectPositionDialog.getSelectedTimelined(); - - copyOrMoveTags(getDependenciesSet(items), true, timelined, position); + + copyOrMoveTags(getDependenciesSet(items), true, timelined, position); } private void openSwfInsideActionPerformed(ActionEvent evt) { @@ -2558,7 +2589,7 @@ public class TagTreeContextMenu extends JPopupMenu { break; case KIND_MOVETODEPS: moveTagWithDependenciesToActionPerformed(ae, items, targetSwf); - break; + break; case KIND_COPYTO: copyTagToActionPerformed(ae, items, targetSwf); break; @@ -2856,4 +2887,76 @@ public class TagTreeContextMenu extends JPopupMenu { mainPanel.importSymbolClass(swf); } + public void moveUpActionPerformed(ActionEvent evt) { + moveUpDown(getTree().getCurrentTreeItem(), true); + } + + public void moveDownActionPerformed(ActionEvent evt) { + moveUpDown(getTree().getCurrentTreeItem(), false); + } + + public void moveUpDown(TreeItem item, boolean up) { + if ((item instanceof SWF) || (item instanceof SWFList)) { + mainPanel.moveSwfListUpDown(item, up); + return; + } + if (!(item instanceof Tag)) { + return; + } + if (getTree() != mainPanel.tagListTree) { + return; + } + Set itemsToMove = new HashSet<>(); + itemsToMove.add(item); + Tag tag = (Tag) item; + int index = tag.getTimelined().indexOfTag(tag); + Tag position = null; + Timelined timelined = null; + ReadOnlyTagList tags = tag.getTimelined().getTags(); + if (up) { + + if (index == 0) { + if (tag.getTimelined() instanceof SWF) { + return; + } + //move one level UP + position = (DefineSpriteTag) tag.getTimelined(); + } else { + index = tag.getTimelined().indexOfTag(tag); + index--; + + position = tag.getTimelined().getTags().get(index); + } + timelined = position.getTimelined(); + } else { + if (index == tags.size() - 1) { + if (tag.getTimelined() instanceof SWF) { + return; + } + timelined = ((DefineSpriteTag) tag.getTimelined()).getTimelined(); + index = timelined.getTags().indexOf((DefineSpriteTag) tag.getTimelined()); + index++; + if (index >= timelined.getTags().size()) { + position = null; + } else { + position = timelined.getTags().get(index); + } + } else { + timelined = tag.getTimelined(); + index = timelined.indexOfTag(tag); + index += 2; + if (index >= timelined.getTags().size()) { + position = null; + } else { + position = timelined.getTags().get(index); + } + } + } + copyOrMoveTags(itemsToMove, true, timelined, position); + + TreePath path = getTree().getModel().getTreePath(item); + getTree().setSelectionPath(path); + getTree().scrollPathToVisible(path); + mainPanel.repaintTree(); + } } diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java index a0091e981..082c8cece 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java @@ -109,7 +109,8 @@ public class TagTreeModel extends AbstractTagTreeModel { } public void updateSwfs(CollectionChangedEvent e) { - if (e.getAction() != CollectionChangedAction.ADD) { + if (e.getAction() != CollectionChangedAction.ADD && + e.getAction() != CollectionChangedAction.MOVE) { List toRemove = new ArrayList<>(); for (SWF swf : swfInfos.keySet()) { SWF swf2 = swf.getRootSwf(); @@ -134,6 +135,12 @@ public class TagTreeModel extends AbstractTagTreeModel { fireTreeNodesRemoved(new TreeModelEvent(this, rootPath, new int[]{e.getOldIndex()}, new Object[]{e.getOldItem()})); break; } + /*case MOVE: { + TreePath rootPath = new TreePath(new Object[]{root}); + fireTreeNodesRemoved(new TreeModelEvent(this, rootPath, new int[]{e.getOldIndex()}, new Object[]{e.getOldItem()})); + fireTreeNodesInserted(new TreeModelEvent(this, rootPath, new int[]{e.getNewIndex()}, new Object[]{e.getNewItem()})); + break; + }*/ default: fireTreeStructureChanged(new TreeModelEvent(this, new TreePath(root))); }