Added: Optimized (faster) context menu for large SWF trees

This commit is contained in:
Jindra Petřík
2024-08-03 13:56:09 +02:00
parent 08f7fe3968
commit fc8791380f
5 changed files with 214 additions and 59 deletions

View File

@@ -42,6 +42,7 @@ All notable changes to this project will be documented in this file.
- [#873] Context menu items are organized with separators and the order is more intuitive
- [#1644] Save all button - has priority over standard Save button
- Exe export mode can be selected in in Save EXE dialog (select filetype) - wrapper or projectors
- Optimized (faster) context menu for large SWF trees
### Fixed
- Debugger - getting children of top level variables

View File

@@ -39,9 +39,10 @@ import com.jpexs.decompiler.flash.treeitems.OpenableList;
import com.jpexs.decompiler.flash.treeitems.TreeItem;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import javax.swing.event.TreeModelEvent;
import javax.swing.tree.TreePath;
@@ -59,7 +60,7 @@ public class TagListTreeModel extends AbstractTagTreeModel {
private Map<SWF, HeaderItem> swfHeaders = new HashMap<>();
private final Map<ABCContainerTag, ClassesListTreeModel> abcTagsClassesTree = new WeakHashMap<>();
private final Map<ABC, ClassesListTreeModel> abcClassesTree = new WeakHashMap<>();
private final Map<ABC, ClassesListTreeModel> abcClassesTree = new WeakHashMap<>();
public TagListTreeModel(List<OpenableList> swfs) {
this.swfs = swfs;
@@ -105,6 +106,14 @@ public class TagListTreeModel extends AbstractTagTreeModel {
@Override
public TreeItem getChild(Object parent, int index) {
TreeItem result = getChildInternal(parent, index);
if (result != null) {
itemToParentCache.put(result, (TreeItem) parent);
}
return result;
}
private TreeItem getChildInternal(Object parent, int index) {
if (getChildCount(parent) == 0) {
return null;
}
@@ -323,6 +332,14 @@ public class TagListTreeModel extends AbstractTagTreeModel {
@Override
public List<? extends TreeItem> getAllChildren(Object parent) {
List<? extends TreeItem> ret = getAllChildrenInternal(parent);
for (TreeItem item : ret) {
itemToParentCache.put(item, (TreeItem) parent);
}
return ret;
}
private List<? extends TreeItem> getAllChildrenInternal(Object parent) {
TreeItem parentNode = (TreeItem) parent;
if (parentNode == root) {
List<TreeItem> result = new ArrayList<>(swfs.size());
@@ -378,6 +395,47 @@ public class TagListTreeModel extends AbstractTagTreeModel {
return ret;
}
@Override
protected void searchTreeItemMulti(List<TreeItem> objs, TreeItem parent, List<TreeItem> path, Map<TreeItem, List<TreeItem>> result) {
for (TreeItem n : getAllChildren(parent)) {
List<TreeItem> newPath = new ArrayList<>();
newPath.addAll(path);
newPath.add(n);
for (TreeItem obj : objs) {
if (obj == n) { //Equals or == ???
result.put(obj, newPath);
}
}
searchTreeItemMulti(objs, n, newPath, result);
}
}
@Override
protected void searchTreeItemParentMulti(List<TreeItem> objs, TreeItem parent, Map<TreeItem, TreeItem> result) {
for (TreeItem n : getAllChildren(parent)) {
for (TreeItem obj : objs) {
if (obj == n) { //Equals or == ???
result.put(obj, parent);
if (result.size() == objs.size()) {
return;
}
}
}
searchTreeItemParentMulti(objs, n, result);
if (result.size() == objs.size()) {
return;
}
}
}
@Override
protected List<TreeItem> searchTreeItem(TreeItem obj, TreeItem parent, List<TreeItem> path) {
List<TreeItem> ret = null;
@@ -386,7 +444,7 @@ public class TagListTreeModel extends AbstractTagTreeModel {
newPath.addAll(path);
newPath.add(n);
if (Objects.equals(obj, n)) {
if (obj == n) { //Equals or == ???
return newPath;
}

View File

@@ -26,6 +26,7 @@ import com.jpexs.decompiler.flash.treeitems.Openable;
import com.jpexs.decompiler.flash.treeitems.TreeItem;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
@@ -45,6 +46,8 @@ public abstract class AbstractTagTreeModel implements TreeModel {
public abstract void updateSwfs(CollectionChangedEvent e);
private Map<TreeItem, Integer> indices = new WeakHashMap<>();
protected Map<TreeItem, TreeItem> itemToParentCache = new WeakHashMap<>();
public final void calculateCollisions() {
Map<TreeItem, Integer> indices = new WeakHashMap<>();
@@ -144,6 +147,47 @@ public abstract class AbstractTagTreeModel implements TreeModel {
protected abstract List<TreeItem> searchTreeItem(TreeItem obj, TreeItem parent, List<TreeItem> path);
protected abstract void searchTreeItemMulti(List<TreeItem> objs, TreeItem parent, List<TreeItem> path, Map<TreeItem, List<TreeItem>> result);
protected abstract void searchTreeItemParentMulti(List<TreeItem> objs, TreeItem parent, Map<TreeItem, TreeItem> result);
public Map<TreeItem, TreeItem> getTreePathParentMulti(List<TreeItem> objs) {
Map<TreeItem, TreeItem> result = new IdentityHashMap<>();
for (TreeItem item : objs) {
if (itemToParentCache.containsKey(item)) {
result.put(item, itemToParentCache.get(item));
} else {
break;
}
}
if (result.size() == objs.size()) {
return result;
}
TreeItem root = getRoot();
//SLOW way
searchTreeItemParentMulti(objs, root, result);
return result;
}
public Map<TreeItem, TreePath> getTreePathMulti(List<TreeItem> objs) {
TreeItem root = getRoot();
List<TreeItem> path = new ArrayList<>();
path.add(root);
Map<TreeItem, List<TreeItem>> paths = new IdentityHashMap<>();
searchTreeItemMulti(objs, root, path, paths);
Map<TreeItem, TreePath> result = new IdentityHashMap<>();
for (TreeItem item : paths.keySet()) {
List<TreeItem> p = paths.get(item);
TreePath tp = new TreePath(p.toArray(new Object[p.size()]));
result.put(item, tp);
}
return result;
}
public TreePath getTreePath(TreeItem obj) {
List<TreeItem> path = new ArrayList<>();
TreeItem root = getRoot();

View File

@@ -1158,14 +1158,12 @@ public class TagTreeContextMenu extends JPopupMenu {
if (parent == null) {
allSelectedSameParent = false;
} else {
for (TreeItem item : items) {
TreePath currentParent = model.getTreePath(item).getParentPath();
if (!currentParent.equals(parent)) {
allSelectedSameParent = false;
break;
}
}
Map<TreeItem, TreeItem> paths = model.getTreePathParentMulti(items);
Set<TreeItem> parentSet = new LinkedIdentityHashSet<>();
parentSet.addAll(paths.values());
if (parentSet.size() != 1) {
allSelectedSameParent = false;
}
}
}

View File

@@ -401,7 +401,91 @@ public class TagTreeModel extends AbstractTagTreeModel {
public Frame getFrame(SWF swf, Timelined t, int frame) {
return searchForFrame(swf, swf, t, frame);
}
@Override
protected void searchTreeItemMulti(List<TreeItem> objs, TreeItem parent, List<TreeItem> path, Map<TreeItem, List<TreeItem>> result) {
for (TreeItem n : getAllChildren(parent)) {
List<TreeItem> newPath = new ArrayList<>();
newPath.addAll(path);
newPath.add(n);
for (TreeItem obj : objs) {
if (searchMatches(n, obj)) {
result.put(obj, newPath);
}
}
searchTreeItemMulti(objs, n, newPath, result);
}
}
@Override
protected void searchTreeItemParentMulti(List<TreeItem> objs, TreeItem parent, Map<TreeItem, TreeItem> result) {
for (TreeItem n : getAllChildren(parent)) {
for (TreeItem obj : objs) {
if (searchMatches(n, obj)) { //Equals or == ???
result.put(obj, parent);
}
}
searchTreeItemParentMulti(objs, n, result);
}
}
private boolean searchMatches(TreeItem n, TreeItem obj) {
if (n instanceof AS3Package) {
AS3Package pkg = (AS3Package) n;
if (obj instanceof AS3Package) {
AS3Package opkg = (AS3Package) obj;
if (Objects.equals(pkg.packageName, opkg.packageName) && pkg.getAbc() == opkg.getAbc()) {
return true;
}
}
}
if (n instanceof AS3ClassTreeItem) {
AS3ClassTreeItem te = (AS3ClassTreeItem) n;
if (obj == te) {
return true;
}
}
if (obj instanceof SceneFrame && n instanceof SceneFrame) {
// SceneFrames are always recreated, so compare them by frame and swf
SceneFrame nds = (SceneFrame) n;
SceneFrame objs = (SceneFrame) obj;
if (objs.getFrame().frame == nds.getFrame().frame && objs.getOpenable() == nds.getOpenable()) {
return true;
}
}
if (obj instanceof FolderItem && n instanceof FolderItem) {
// FolderItems are always recreated, so compare them by name and swf
FolderItem nds = (FolderItem) n;
FolderItem objs = (FolderItem) obj;
if (objs.getName().equals(nds.getName()) && objs.swf == nds.swf) {
return true;
}
} else {
TreeItem objNoTs = obj;
if (obj instanceof TagScript) {
objNoTs = ((TagScript) obj).getTag();
}
TreeItem nNoTs = n;
if (n instanceof TagScript) {
nNoTs = ((TagScript) n).getTag();
}
if (objNoTs == nNoTs) {
return true;
}
}
return false;
}
@Override
protected List<TreeItem> searchTreeItem(TreeItem obj, TreeItem parent, List<TreeItem> path) {
List<TreeItem> ret = null;
@@ -410,54 +494,8 @@ public class TagTreeModel extends AbstractTagTreeModel {
newPath.addAll(path);
newPath.add(n);
if (n instanceof AS3Package) {
AS3Package pkg = (AS3Package) n;
if (obj instanceof AS3Package) {
AS3Package opkg = (AS3Package) obj;
if (Objects.equals(pkg.packageName, opkg.packageName) && pkg.getAbc() == opkg.getAbc()) {
return newPath;
}
}
}
if (n instanceof AS3ClassTreeItem) {
AS3ClassTreeItem te = (AS3ClassTreeItem) n;
if (obj == te) {
return newPath;
}
}
if (obj instanceof SceneFrame && n instanceof SceneFrame) {
// SceneFrames are always recreated, so compare them by frame and swf
SceneFrame nds = (SceneFrame) n;
SceneFrame objs = (SceneFrame) obj;
if (objs.getFrame().frame == nds.getFrame().frame && objs.getOpenable() == nds.getOpenable()) {
return newPath;
}
}
if (obj instanceof FolderItem && n instanceof FolderItem) {
// FolderItems are always recreated, so compare them by name and swf
FolderItem nds = (FolderItem) n;
FolderItem objs = (FolderItem) obj;
if (objs.getName().equals(nds.getName()) && objs.swf == nds.swf) {
return newPath;
}
} else {
TreeItem objNoTs = obj;
if (obj instanceof TagScript) {
objNoTs = ((TagScript) obj).getTag();
}
TreeItem nNoTs = n;
if (n instanceof TagScript) {
nNoTs = ((TagScript) n).getTag();
}
if (objNoTs == nNoTs) {
return newPath;
}
if (searchMatches(n, obj)) {
return newPath;
}
ret = searchTreeItem(obj, n, newPath);
@@ -520,6 +558,14 @@ public class TagTreeModel extends AbstractTagTreeModel {
@Override
public List<? extends TreeItem> getAllChildren(Object parent) {
List<? extends TreeItem> ret = getAllChildrenInternal(parent);
for (TreeItem item : ret) {
itemToParentCache.put(item, (TreeItem) parent);
}
return ret;
}
private List<? extends TreeItem> getAllChildrenInternal(Object parent) {
TreeItem parentNode = (TreeItem) parent;
List<TreeItem> result = new ArrayList<>();
if (parentNode instanceof CharacterTag) {
@@ -616,6 +662,14 @@ public class TagTreeModel extends AbstractTagTreeModel {
@Override
public TreeItem getChild(Object parent, int index) {
TreeItem result = getChildInternal(parent, index);
if (result != null) {
itemToParentCache.put(result, (TreeItem) parent);
}
return result;
}
private TreeItem getChildInternal(Object parent, int index) {
if (getChildCount(parent) == 0) {
return null;
}