mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-06-02 20:04:37 +00:00
478 lines
18 KiB
Java
478 lines
18 KiB
Java
/*
|
|
* Copyright (C) 2010-2023 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package com.jpexs.decompiler.flash.gui;
|
|
|
|
import com.jpexs.decompiler.flash.SWF;
|
|
import com.jpexs.decompiler.flash.configuration.Configuration;
|
|
import com.jpexs.decompiler.flash.search.SearchResult;
|
|
import com.jpexs.decompiler.flash.treeitems.Openable;
|
|
import java.awt.BorderLayout;
|
|
import java.awt.CardLayout;
|
|
import java.awt.Component;
|
|
import java.awt.Container;
|
|
import java.awt.Dimension;
|
|
import java.awt.FlowLayout;
|
|
import java.awt.Graphics;
|
|
import java.awt.Insets;
|
|
import java.awt.Rectangle;
|
|
import java.awt.Window;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.KeyAdapter;
|
|
import java.awt.event.KeyEvent;
|
|
import java.awt.event.KeyListener;
|
|
import java.awt.event.MouseAdapter;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.event.MouseListener;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Enumeration;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import javax.swing.Box;
|
|
import javax.swing.BoxLayout;
|
|
import javax.swing.DefaultListModel;
|
|
import javax.swing.ImageIcon;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JComponent;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JList;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JTree;
|
|
import javax.swing.event.TreeExpansionEvent;
|
|
import javax.swing.event.TreeWillExpandListener;
|
|
import javax.swing.plaf.basic.BasicTreeUI;
|
|
import javax.swing.tree.DefaultTreeCellRenderer;
|
|
import javax.swing.tree.DefaultTreeModel;
|
|
import javax.swing.tree.ExpandVetoException;
|
|
import javax.swing.tree.TreeNode;
|
|
import javax.swing.tree.TreePath;
|
|
|
|
/**
|
|
*
|
|
* @author JPEXS
|
|
* @param <E> Element to search
|
|
*/
|
|
public class SearchResultsDialog<E extends SearchResult> extends AppDialog {
|
|
|
|
private final JTree resultsTree;
|
|
|
|
private final JList<SearchResult> resultsList;
|
|
|
|
private final JPanel resultsPanel;
|
|
|
|
private final boolean regExp;
|
|
|
|
private final List<SearchListener<E>> listeners;
|
|
|
|
private final JButton gotoButton = new JButton(translate("button.goto"));
|
|
|
|
private final JButton closeButton = new JButton(translate("button.close"));
|
|
|
|
private String text;
|
|
private final boolean ignoreCase;
|
|
|
|
private Map<Openable, List<SearchResult>> openableToResults = new LinkedHashMap<>();
|
|
|
|
public SearchResultsDialog(Window owner, String text, boolean ignoreCase, boolean regExp, List<SearchListener<E>> listeners) {
|
|
super(owner);
|
|
setTitle(translate("dialog.title").replace("%text%", text));
|
|
this.text = text;
|
|
Container cnt = getContentPane();
|
|
resultsTree = new JTree(new BasicTreeNode("root"));
|
|
resultsList = new JList<>(new DefaultListModel<>());
|
|
this.regExp = regExp;
|
|
this.listeners = listeners;
|
|
|
|
gotoButton.addActionListener(this::gotoButtonActionPerformed);
|
|
closeButton.addActionListener(this::closeButtonActionPerformed);
|
|
|
|
JPanel paramsPanel = new JPanel();
|
|
paramsPanel.setLayout(new FlowLayout());
|
|
JLabel searchTextLabel = new JLabel(AppDialog.translateForDialog("label.searchtext", SearchDialog.class) + text);
|
|
JLabel ignoreCaseLabel = new JLabel(AppDialog.translateForDialog("checkbox.ignorecase", SearchDialog.class) + ": " + (ignoreCase ? AppStrings.translate("yes") : AppStrings.translate("no")));
|
|
JLabel regExpLabel = new JLabel(AppDialog.translateForDialog("checkbox.regexp", SearchDialog.class) + ": " + (regExp ? AppStrings.translate("yes") : AppStrings.translate("no")));
|
|
paramsPanel.add(ignoreCaseLabel);
|
|
paramsPanel.add(Box.createRigidArea(new Dimension(10, 0)));
|
|
paramsPanel.add(regExpLabel);
|
|
|
|
JPanel buttonsPanel = new JPanel();
|
|
buttonsPanel.setLayout(new FlowLayout());
|
|
buttonsPanel.add(gotoButton);
|
|
buttonsPanel.add(closeButton);
|
|
KeyListener keyListener = new KeyAdapter() {
|
|
@Override
|
|
public void keyReleased(KeyEvent e) {
|
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
|
gotoElement();
|
|
}
|
|
}
|
|
};
|
|
resultsTree.addKeyListener(keyListener);
|
|
|
|
resultsList.addKeyListener(keyListener);
|
|
|
|
MouseListener mouseListener = new MouseAdapter() {
|
|
@Override
|
|
public void mouseClicked(MouseEvent e) {
|
|
if (e.getClickCount() == 2) {
|
|
gotoElement();
|
|
}
|
|
}
|
|
};
|
|
resultsTree.addMouseListener(mouseListener);
|
|
resultsList.addMouseListener(mouseListener);
|
|
|
|
resultsTree.setRootVisible(false);
|
|
|
|
resultsTree.addTreeWillExpandListener(new TreeWillExpandListener() {
|
|
@Override
|
|
public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
|
|
throw new ExpandVetoException(event, "Collapsing tree not allowed");
|
|
}
|
|
});
|
|
|
|
final ImageIcon flashIcon = View.getIcon("flash16");
|
|
|
|
resultsTree.setCellRenderer(new DefaultTreeCellRenderer() {
|
|
@Override
|
|
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
|
Component c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
|
|
if (c instanceof JLabel) {
|
|
JLabel label = (JLabel) c;
|
|
BasicTreeNode node = (BasicTreeNode) value;
|
|
if (node.getData() instanceof SWF) {
|
|
label.setIcon(flashIcon);
|
|
} else {
|
|
label.setIcon(null);
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
});
|
|
|
|
resultsTree.setUI(new BasicTreeUI() {
|
|
|
|
@Override
|
|
public void paint(Graphics g, JComponent c) {
|
|
if (tree != c) {
|
|
throw new InternalError("incorrect component");
|
|
}
|
|
|
|
// Should never happen if installed for a UI
|
|
if (treeState == null) {
|
|
return;
|
|
}
|
|
|
|
Rectangle paintBounds = g.getClipBounds();
|
|
Insets insets = tree.getInsets();
|
|
TreePath initialPath = getClosestPathForLocation(tree, 0, paintBounds.y);
|
|
Enumeration<?> paintingEnumerator = treeState.getVisiblePathsFrom(initialPath);
|
|
int row = treeState.getRowForPath(initialPath);
|
|
int endY = paintBounds.y + paintBounds.height;
|
|
|
|
drawingCache.clear();
|
|
|
|
if (initialPath != null && paintingEnumerator != null) {
|
|
TreePath parentPath = initialPath;
|
|
|
|
// Draw the lines, knobs, and rows
|
|
// Find each parent and have them draw a line to their last child
|
|
parentPath = parentPath.getParentPath();
|
|
while (parentPath != null) {
|
|
paintVerticalPartOfLeg(g, paintBounds, insets, parentPath);
|
|
drawingCache.put(parentPath, Boolean.TRUE);
|
|
parentPath = parentPath.getParentPath();
|
|
}
|
|
|
|
boolean done = false;
|
|
// Information for the node being rendered.
|
|
boolean isExpanded;
|
|
boolean hasBeenExpanded;
|
|
boolean isLeaf;
|
|
Rectangle boundsBuffer = new Rectangle();
|
|
Rectangle bounds;
|
|
TreePath path;
|
|
boolean rootVisible = isRootVisible();
|
|
|
|
while (!done && paintingEnumerator.hasMoreElements()) {
|
|
path = (TreePath) paintingEnumerator.nextElement();
|
|
if (path != null) {
|
|
isLeaf = treeModel.isLeaf(path.getLastPathComponent());
|
|
if (isLeaf) {
|
|
isExpanded = hasBeenExpanded = false;
|
|
} else {
|
|
isExpanded = treeState.getExpandedState(path);
|
|
hasBeenExpanded = tree.hasBeenExpanded(path);
|
|
}
|
|
bounds = getPathBounds(path, insets, boundsBuffer);
|
|
if (bounds == null) {
|
|
// This will only happen if the model changes out
|
|
// from under us (usually in another thread).
|
|
// Swing isn't multithreaded, but I'll put this
|
|
// check in anyway.
|
|
return;
|
|
}
|
|
// See if the vertical line to the parent has been drawn.
|
|
parentPath = path.getParentPath();
|
|
if (parentPath != null) {
|
|
if (drawingCache.get(parentPath) == null) {
|
|
paintVerticalPartOfLeg(g, paintBounds,
|
|
insets, parentPath);
|
|
drawingCache.put(parentPath, Boolean.TRUE);
|
|
}
|
|
paintHorizontalPartOfLeg(g, paintBounds, insets,
|
|
bounds, path, row,
|
|
isExpanded,
|
|
hasBeenExpanded, isLeaf);
|
|
} else if (rootVisible && row == 0) {
|
|
paintHorizontalPartOfLeg(g, paintBounds, insets,
|
|
bounds, path, row,
|
|
isExpanded,
|
|
hasBeenExpanded, isLeaf);
|
|
}
|
|
if (shouldPaintExpandControl(path, row, isExpanded,
|
|
hasBeenExpanded, isLeaf)) {
|
|
paintExpandControl(g, paintBounds, insets, bounds,
|
|
path, row, isExpanded,
|
|
hasBeenExpanded, isLeaf);
|
|
}
|
|
paintRow(g, paintBounds, insets, bounds, path,
|
|
row, isExpanded, hasBeenExpanded, isLeaf);
|
|
if ((bounds.y + bounds.height) >= endY) {
|
|
done = true;
|
|
}
|
|
} else {
|
|
done = true;
|
|
}
|
|
row++;
|
|
}
|
|
}
|
|
|
|
paintDropLine(g);
|
|
|
|
// Empty out the renderer pane, allowing renderers to be gc'ed.
|
|
rendererPane.removeAll();
|
|
|
|
drawingCache.clear();
|
|
}
|
|
|
|
@Override
|
|
public Rectangle getPathBounds(JTree tree, TreePath path) {
|
|
if (tree != null && treeState != null) {
|
|
return getPathBounds(path, tree.getInsets(), new Rectangle());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private Rectangle getPathBounds(
|
|
TreePath path, Insets insets, Rectangle bounds) {
|
|
bounds = treeState.getBounds(path, bounds);
|
|
if (bounds != null) {
|
|
bounds.width = tree.getWidth();
|
|
bounds.y += insets.top;
|
|
}
|
|
return bounds;
|
|
}
|
|
});
|
|
|
|
cnt.setLayout(new BorderLayout());
|
|
|
|
resultsPanel = new JPanel(new CardLayout());
|
|
resultsPanel.add(resultsTree, "tree");
|
|
resultsPanel.add(resultsList, "list");
|
|
|
|
JScrollPane sp = new FasterScrollPane(resultsPanel);
|
|
sp.setPreferredSize(new Dimension(300, 300));
|
|
cnt.add(sp, BorderLayout.CENTER);
|
|
|
|
JPanel bottomPanel = new JPanel();
|
|
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS));
|
|
JPanel searchTextPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
|
|
searchTextPanel.add(searchTextLabel);
|
|
if (Configuration.parametersPanelInSearchResults.get()) {
|
|
bottomPanel.add(searchTextPanel);
|
|
bottomPanel.add(paramsPanel);
|
|
bottomPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
|
}
|
|
bottomPanel.add(buttonsPanel);
|
|
cnt.add(bottomPanel, BorderLayout.SOUTH);
|
|
pack();
|
|
View.centerScreen(this);
|
|
View.setWindowIcon(this);
|
|
this.ignoreCase = ignoreCase;
|
|
}
|
|
|
|
private static class BasicTreeNode implements TreeNode {
|
|
|
|
private final List<TreeNode> children = new ArrayList<>();
|
|
private TreeNode parent;
|
|
private final Object data;
|
|
|
|
public BasicTreeNode(Object data) {
|
|
this.data = data;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return data.toString();
|
|
}
|
|
|
|
public Object getData() {
|
|
return data;
|
|
}
|
|
|
|
public void addChild(TreeNode node) {
|
|
children.add(node);
|
|
}
|
|
|
|
public void setParent(TreeNode parent) {
|
|
this.parent = parent;
|
|
}
|
|
|
|
@Override
|
|
public TreeNode getChildAt(int childIndex) {
|
|
return children.get(childIndex);
|
|
}
|
|
|
|
@Override
|
|
public int getChildCount() {
|
|
return children.size();
|
|
}
|
|
|
|
@Override
|
|
public TreeNode getParent() {
|
|
return parent;
|
|
}
|
|
|
|
@Override
|
|
public int getIndex(TreeNode node) {
|
|
return children.indexOf(node);
|
|
}
|
|
|
|
@Override
|
|
public boolean getAllowsChildren() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean isLeaf() {
|
|
return children.isEmpty();
|
|
}
|
|
|
|
@Override
|
|
public Enumeration<? extends TreeNode> children() {
|
|
return Collections.enumeration(children);
|
|
}
|
|
|
|
}
|
|
|
|
public void setResults(List<E> results) {
|
|
openableToResults.clear();
|
|
for (E e : results) {
|
|
if (!openableToResults.containsKey(e.getOpenable())) {
|
|
openableToResults.put(e.getOpenable(), new ArrayList<>());
|
|
}
|
|
openableToResults.get(e.getOpenable()).add(e);
|
|
}
|
|
updateModel();
|
|
}
|
|
|
|
private void updateModel() {
|
|
boolean showSwfTitles = openableToResults.size() > 1;
|
|
|
|
if (showSwfTitles) {
|
|
BasicTreeNode rootNode = new BasicTreeNode("root");
|
|
List<BasicTreeNode> swfNodes = new ArrayList<>();
|
|
for (Openable s : openableToResults.keySet()) {
|
|
BasicTreeNode swfNode = new BasicTreeNode(s);
|
|
if (showSwfTitles) {
|
|
rootNode.addChild(swfNode);
|
|
swfNode.setParent(rootNode);
|
|
swfNodes.add(swfNode);
|
|
}
|
|
for (SearchResult r : openableToResults.get(s)) {
|
|
BasicTreeNode rNode = new BasicTreeNode(r);
|
|
if (showSwfTitles) {
|
|
swfNode.addChild(rNode);
|
|
rNode.setParent(swfNode);
|
|
} else {
|
|
rootNode.addChild(rNode);
|
|
rNode.setParent(rootNode);
|
|
}
|
|
}
|
|
}
|
|
resultsTree.setModel(new DefaultTreeModel(rootNode, false));
|
|
for (TreeNode t : swfNodes) {
|
|
TreePath tp = new TreePath(new Object[]{rootNode, t});
|
|
resultsTree.expandPath(tp);
|
|
}
|
|
} else {
|
|
DefaultListModel<SearchResult> model = (DefaultListModel<SearchResult>) resultsList.getModel();
|
|
model.clear();
|
|
if (!openableToResults.isEmpty()) {
|
|
List<SearchResult> elements = openableToResults.get(openableToResults.keySet().iterator().next());
|
|
for (SearchResult e : elements) {
|
|
model.addElement(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
((CardLayout) resultsPanel.getLayout()).show(resultsPanel, showSwfTitles ? "tree" : "list");
|
|
}
|
|
|
|
public void removeSwf(SWF swf) {
|
|
openableToResults.remove(swf);
|
|
updateModel();
|
|
}
|
|
|
|
public boolean isEmpty() {
|
|
return openableToResults.isEmpty();
|
|
}
|
|
|
|
private void gotoButtonActionPerformed(ActionEvent evt) {
|
|
gotoElement();
|
|
}
|
|
|
|
private void closeButtonActionPerformed(ActionEvent evt) {
|
|
setVisible(false);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private void gotoElement() {
|
|
if (openableToResults.size() > 1) {
|
|
BasicTreeNode selection = (BasicTreeNode) resultsTree.getLastSelectedPathComponent();
|
|
if (selection.getData() instanceof SearchResult) {
|
|
for (SearchListener<E> listener : listeners) {
|
|
listener.updateSearchPos(text, ignoreCase, regExp, (E) selection.getData());
|
|
}
|
|
}
|
|
} else {
|
|
for (SearchListener<E> listener : listeners) {
|
|
listener.updateSearchPos(text, ignoreCase, regExp, (E) resultsList.getSelectedValue());
|
|
}
|
|
}
|
|
}
|
|
}
|