package club.bytecode.the.jda.gui.navigation; import club.bytecode.the.jda.FileContainer; import club.bytecode.the.jda.FileDrop; import club.bytecode.the.jda.JDA; import club.bytecode.the.jda.Resources; import club.bytecode.the.jda.gui.JDAWindow; import club.bytecode.the.jda.gui.fileviewer.ViewerFile; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.*; import java.awt.font.FontRenderContext; import java.awt.geom.Rectangle2D; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.Map.Entry; import java.util.function.Consumer; /** * The file navigation pane. * * @author Konloch * @author WaterWolf * @author afffsdd */ @SuppressWarnings("serial") public class FileNavigationPane extends JDAWindow { private static final String quickSearchText = "File search"; JCheckBox matchCase = new JCheckBox("Match case"); FileNode treeRoot = new FileNode("Loaded Files:"); FileTree tree = new FileTree(treeRoot); final JTextField quickSearch = new JTextField(quickSearchText); public transient KeyAdapter search = new KeyAdapter() { @Override public void keyPressed(final KeyEvent ke) { if (ke.getKeyCode() == KeyEvent.VK_ENTER) { final String qt = quickSearch.getText(); quickSearch.setText(""); quickSearch(qt); } else if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) { tree.grabFocus(); } JDA.checkHotKey(ke); } }; private void quickSearch(String qt) { if (!matchCase.isSelected()) qt = qt.toLowerCase(); String[] path = qt.split("\\."); String searchFilename = path[path.length - 1]; FileNode curNode = treeRoot; @SuppressWarnings("unchecked") Enumeration<FileNode> enums = curNode.depthFirstEnumeration(); while (enums != null && enums.hasMoreElements()) { FileNode node = enums.nextElement(); if (!node.isLeaf()) { continue; } // Check filename String leafFilename = (String) (node.getUserObject()); if (!matchCase.isSelected()) leafFilename = leafFilename.toLowerCase(); if (!leafFilename.contains(searchFilename)) { continue; } // Check for path match TreeNode pathArray[] = node.getPath(); int k = 0; StringBuilder fullPath = new StringBuilder(); while (pathArray != null && k < pathArray.length) { FileNode n = (FileNode) pathArray[k]; String s = (n.getUserObject()).toString(); fullPath.append(s); if (k++ != pathArray.length - 1) { fullPath.append("."); } } String fullPathString = fullPath.toString(); if (!matchCase.isSelected()) fullPathString = fullPathString.toLowerCase(); if (fullPathString.contains(qt)) { // Match found final TreePath pathn = new TreePath(node.getPath()); tree.setSelectionPath(pathn.getParentPath()); tree.setSelectionPath(pathn); tree.makeVisible(pathn); tree.scrollPathToVisible(pathn); break; } } } public FileNavigationPane() { super("ClassNavigation", "File Navigator", Resources.fileViewerIcon); tree.setRootVisible(false); tree.setShowsRootHandles(true); quickSearch.setForeground(Color.gray); setMinimumSize(new Dimension(200, 50)); this.tree.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { openTreePath(tree.getPathForLocation(e.getX(), e.getY())); } }); tree.setComponentPopupMenu(new TreeContextMenu(this)); tree.setInheritsPopupMenu(true); this.tree.addKeyListener(new KeyListener() { public void keyTyped(KeyEvent e) { } public void keyPressed(KeyEvent e) { JDA.checkHotKey(e); } @Override public void keyReleased(KeyEvent arg0) { if (arg0.getKeyCode() == KeyEvent.VK_ENTER) { if (arg0.getSource() instanceof FileTree) { FileTree tree = (FileTree) arg0.getSource(); openTreePath(tree.getSelectionPath()); } } else if (arg0.getKeyCode() == KeyEvent.VK_F && arg0.isControlDown()) { quickSearch.grabFocus(); } } }); quickSearch.addKeyListener(search); quickSearch.addFocusListener(new FocusListener() { @Override public void focusGained(final FocusEvent arg0) { if (quickSearch.getText().equals(quickSearchText)) { quickSearch.setText(null); quickSearch.setForeground(Color.black); } } @Override public void focusLost(final FocusEvent arg0) { if (quickSearch.getText().isEmpty()) { quickSearch.setText(quickSearchText); quickSearch.setForeground(Color.gray); } } }); getContentPane().setLayout(new BorderLayout()); getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER); JPanel p2 = new JPanel(); p2.setLayout(new BorderLayout()); p2.add(quickSearch, BorderLayout.NORTH); JPanel p3 = new JPanel(new BorderLayout()); matchCase.setSelected(true); p3.add(matchCase, BorderLayout.WEST); JPanel p4 = new JPanel(new BorderLayout()); p3.add(p4, BorderLayout.EAST); p2.add(p3, BorderLayout.SOUTH); getContentPane().add(p2, BorderLayout.SOUTH); new FileDrop(this, files -> { if (files.length < 1) return; JDA.openFiles(files, true); }); } public static Dimension defaultDimension = new Dimension(350, -35); public static Point defaultPosition = new Point(0, 0); @Override public Dimension getDefaultSize() { return defaultDimension; } @Override public Point getDefaultPosition() { return defaultPosition; } public void openClassFileToWorkSpace(ViewerFile file) { JDA.viewer.openClassFile(file); } public void openFileToWorkSpace(ViewerFile file) { JDA.viewer.openFile(file); } /** * Add tree element. * If parent is null, it will be added to the root node. */ public FileNode addTreeElement(FileContainer container, FileNode parent) { if (parent == null) parent = treeRoot; FileNode root = new FileNode(container, isJava(container)); parent.add(root); JDATreeCellRenderer renderer = new JDATreeCellRenderer(); tree.setCellRenderer(renderer); if (container.files.size() > 1) { for (final Entry<String, byte[]> entry : container.files.entrySet()) { String name = entry.getKey(); final String[] spl = name.split("/"); FileNode parentNode = root; if (spl.length <= 1) { root.add(new FileNode(name, parentNode.isJava)); } else { for (final String s : spl) { FileNode child = null; for (int i = 0; i < parentNode.getChildCount(); i++) { if (((FileNode) parentNode.getChildAt(i)).getUserObject().equals(s)) { child = (FileNode) parentNode.getChildAt(i); break; } } if (child == null) { child = new FileNode(s, parentNode.isJava); parentNode.add(child); } parentNode = child; } } } } parent.sort(); tree.expandPath(new TreePath(tree.getModel().getRoot())); tree.updateUI(); return root; } private boolean isJava(FileContainer container) { return container.name.endsWith(".java") || container.name.endsWith(".jar") || container.name.endsWith(".class"); } @SuppressWarnings("rawtypes") void treeDfs(final TreePath parent, Consumer<TreePath> onPreOrder, Consumer<TreePath> onPostOrder) { if (onPreOrder != null) onPreOrder.accept(parent); final TreeNode node = (TreeNode) parent.getLastPathComponent(); if (node.getChildCount() >= 0) { for (final Enumeration e = node.children(); e.hasMoreElements(); ) { final TreeNode n = (TreeNode) e.nextElement(); final TreePath path = parent.pathByAddingChild(n); treeDfs(path, onPreOrder, onPostOrder); } } if (onPostOrder != null) onPostOrder.accept(parent); } public class FileTree extends JTree { private static final long serialVersionUID = -2355167326094772096L; DefaultMutableTreeNode treeRoot; public FileTree(final DefaultMutableTreeNode treeRoot) { super(treeRoot); this.treeRoot = treeRoot; } StringMetrics m = null; @Override public void paint(final Graphics g) { try { super.paint(g); if (m == null) { m = new StringMetrics((Graphics2D) g); } if (treeRoot.getChildCount() < 1) { g.setColor(new Color(0, 0, 0, 100)); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(Color.white); String s = "Drag class/jar/zip here"; g.drawString(s, ((int) ((getWidth() / 2) - (m.getWidth(s) / 2))), getHeight() / 2); } } catch (java.lang.InternalError | java.lang.NullPointerException e) { } } } public class FileNode extends DefaultMutableTreeNode { public final boolean isJava; private static final long serialVersionUID = -8817777566176729571L; public FileNode(final Object o, boolean isJava) { super(o); this.isJava = isJava; } public FileNode(final Object o) { this(o, false); } @Override public void insert(final MutableTreeNode newChild, final int childIndex) { super.insert(newChild, childIndex); } public void sort() { recursiveSort(this); } @SuppressWarnings("unchecked") private void recursiveSort(final FileNode node) { Collections.sort(node.children, nodeComparator); for (FileNode nextNode : (Iterable<FileNode>) node.children) { if (nextNode.getChildCount() > 0) { recursiveSort(nextNode); } } } protected Comparator<FileNode> nodeComparator = (a, b) -> { // Ensure nodes with children are always on top final boolean aEmpty = a.getChildCount() > 0; final boolean bEmpty = b.getChildCount() > 0; if (aEmpty && !bEmpty) return -1; else if (!aEmpty && bEmpty) return 1; // Try insensitive first, but if they are the same insensitively do it case sensitively int compare = a.toString().compareToIgnoreCase(b.toString()); if (compare != 0) return compare; else return a.toString().compareTo(b.toString()); }; } /** * @author http://stackoverflow.com/a/18450804 */ class StringMetrics { Font font; FontRenderContext context; public StringMetrics(Graphics2D g2) { font = g2.getFont(); context = g2.getFontRenderContext(); } Rectangle2D getBounds(String message) { return font.getStringBounds(message, context); } double getWidth(String message) { Rectangle2D bounds = getBounds(message); return bounds.getWidth(); } double getHeight(String message) { Rectangle2D bounds = getBounds(message); return bounds.getHeight(); } } public void resetWorkspace() { treeRoot.removeAllChildren(); tree.repaint(); tree.updateUI(); } public void openTreePath(TreePath path) { if (path == null) return; FileContainer container = null; int containerLevel; for (containerLevel = path.getPathCount() - 1; containerLevel > 0; containerLevel--) { Object o = ((FileNode) path.getPathComponent(containerLevel)).getUserObject(); if (o != null && o instanceof FileContainer) { container = (FileContainer) o; break; } } if (container == null) { System.err.println(path); throw new IllegalStateException("Path isn't parented to a container?"); } final StringBuilder nameBuffer = new StringBuilder(); for (int i = containerLevel + 1; i < path.getPathCount(); i++) { nameBuffer.append(path.getPathComponent(i)); if (i < path.getPathCount() - 1) { nameBuffer.append("/"); } } // single-file thang if (container.files.size() == 1 && nameBuffer.length() == 0) { nameBuffer.append(container.files.keySet().iterator().next()); } ViewerFile file = new ViewerFile(container, nameBuffer.toString()); if (!JDA.hasFile(file)) { // if it's null, it's a directory or some non-leaf tree node tree.expandPath(path); } else if (file.name.endsWith(".class")) { openClassFileToWorkSpace(file); } else { openFileToWorkSpace(file); } } }