Files
jpexs-decompiler/src/com/jpexs/decompiler/flash/gui/SearchResultsDialog.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());
}
}
}
}