diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aa2099fe..0e7702a2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file. ### Changed - #1616 Close SWF menuitem is last in the context menu +- #1620 Search results - Using list component instead of tree when searching in single SWF ## [14.0.0] - 2021-02-24 ### Added diff --git a/src/com/jpexs/decompiler/flash/gui/SearchResultsDialog.java b/src/com/jpexs/decompiler/flash/gui/SearchResultsDialog.java index d844a0867..992cac19c 100644 --- a/src/com/jpexs/decompiler/flash/gui/SearchResultsDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/SearchResultsDialog.java @@ -20,32 +20,46 @@ import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.search.SearchResult; import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; 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.AbstractListModel; import javax.swing.Box; import javax.swing.BoxLayout; +import javax.swing.DefaultListCellRenderer; +import javax.swing.DefaultListModel; +import javax.swing.Icon; 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; @@ -61,6 +75,10 @@ public class SearchResultsDialog extends AppDialog { private final JTree resultsTree; + private final JList resultsList; + + private final JPanel resultsPanel; + private final boolean regExp; private final List> listeners; @@ -80,6 +98,7 @@ public class SearchResultsDialog extends AppDialog { this.text = text; Container cnt = getContentPane(); resultsTree = new JTree(new BasicTreeNode("root")); + resultsList = new JList<>(new DefaultListModel<>()); this.regExp = regExp; this.listeners = listeners; @@ -99,23 +118,28 @@ public class SearchResultsDialog extends AppDialog { buttonsPanel.setLayout(new FlowLayout()); buttonsPanel.add(gotoButton); buttonsPanel.add(closeButton); - resultsTree.addKeyListener(new KeyAdapter() { + KeyListener keyListener = new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { gotoElement(); } } - }); + }; + resultsTree.addKeyListener(keyListener); - resultsTree.addMouseListener(new MouseAdapter() { + 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); @@ -151,8 +175,138 @@ public class SearchResultsDialog extends AppDialog { }); + 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()); - JScrollPane sp = new JScrollPane(resultsTree); + + resultsPanel = new JPanel(new CardLayout()); + resultsPanel.add(resultsTree, "tree"); + resultsPanel.add(resultsList, "list"); + + JScrollPane sp = new JScrollPane(resultsPanel); sp.setPreferredSize(new Dimension(300, 300)); cnt.add(sp, BorderLayout.CENTER); @@ -188,7 +342,6 @@ public class SearchResultsDialog extends AppDialog { return data.toString(); } - public Object getData() { return data; } @@ -251,31 +404,40 @@ public class SearchResultsDialog extends AppDialog { private void updateModel() { boolean showSwfTitles = swfToResults.size() > 1; - BasicTreeNode rootNode = new BasicTreeNode("root"); - List swfNodes = new ArrayList<>(); - for (SWF s : swfToResults.keySet()) { - BasicTreeNode swfNode = new BasicTreeNode(s); - if (showSwfTitles) { - rootNode.addChild(swfNode); - swfNode.setParent(rootNode); - swfNodes.add(swfNode); - } - for (SearchResult r : swfToResults.get(s)) { - BasicTreeNode rNode = new BasicTreeNode(r); + + if (showSwfTitles) { + BasicTreeNode rootNode = new BasicTreeNode("root"); + List swfNodes = new ArrayList<>(); + for (SWF s : swfToResults.keySet()) { + BasicTreeNode swfNode = new BasicTreeNode(s); if (showSwfTitles) { - swfNode.addChild(rNode); - rNode.setParent(swfNode); - } else { - rootNode.addChild(rNode); - rNode.setParent(rootNode); + rootNode.addChild(swfNode); + swfNode.setParent(rootNode); + swfNodes.add(swfNode); + } + for (SearchResult r : swfToResults.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 model = (DefaultListModel) resultsList.getModel(); + model.clear(); + model.addAll(swfToResults.get(swfToResults.keySet().iterator().next())); } - resultsTree.setModel(new DefaultTreeModel(rootNode, false)); - for (TreeNode t : swfNodes) { - TreePath tp = new TreePath(new Object[]{rootNode, t}); - resultsTree.expandPath(tp); - } + + ((CardLayout) resultsPanel.getLayout()).show(resultsPanel, showSwfTitles ? "tree" : "list"); } public void removeSwf(SWF swf) { @@ -297,10 +459,16 @@ public class SearchResultsDialog extends AppDialog { @SuppressWarnings("unchecked") private void gotoElement() { - BasicTreeNode selection = (BasicTreeNode) resultsTree.getLastSelectedPathComponent(); - if (selection.getData() instanceof SearchResult) { + if (swfToResults.size() > 1) { + BasicTreeNode selection = (BasicTreeNode) resultsTree.getLastSelectedPathComponent(); + if (selection.getData() instanceof SearchResult) { + for (SearchListener listener : listeners) { + listener.updateSearchPos(text, ignoreCase, regExp, (E) selection.getData()); + } + } + } else { for (SearchListener listener : listeners) { - listener.updateSearchPos(text, ignoreCase, regExp, (E) selection.getData()); + listener.updateSearchPos(text, ignoreCase, regExp, (E) resultsList.getSelectedValue()); } } }