package com.jvms.i18neditor.editor; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Enumeration; import java.util.List; import java.util.Set; import javax.swing.InputMap; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeWillExpandListener; import javax.swing.tree.ExpandVetoException; import javax.swing.tree.TreePath; import com.google.common.collect.Lists; import com.jvms.i18neditor.util.ResourceKeys; /** * This class represents a tree view for translation keys. * * @author Jacob van Mourik */ public class TranslationTree extends JTree { private final static long serialVersionUID = -2888673305196385241L; public TranslationTree() { super(new TranslationTreeModel()); setupUI(); } public void collapseAll() { for (int i = getRowCount(); i >= 0; i--) { collapseRow(i); } } public void expandAll() { for (int i = 0; i < getRowCount(); i++) { expandRow(i); } } public void expand(List<TranslationTreeNode> nodes) { nodes.forEach(n -> expandPath(new TreePath(n.getPath()))); } public void collapse(List<TranslationTreeNode> nodes) { nodes.forEach(n -> collapsePath(new TreePath(n.getPath()))); } public void updateNodes(Set<String> errorKeys) { TranslationTreeModel model = (TranslationTreeModel) getModel(); Enumeration<TranslationTreeNode> e = model.getEnumeration(); while (e.hasMoreElements()) { TranslationTreeNode n = e.nextElement(); n.setError(errorKeys.contains(n.getKey())); model.nodeChanged(n); } } public void updateNode(String key, boolean error) { TranslationTreeModel model = (TranslationTreeModel) getModel(); TranslationTreeNode node = model.getNodeByKey(key); if (node != null && node.isLeaf()) { node.setError(error); model.nodeWithParentsChanged(node); } } public TranslationTreeNode addNodeByKey(String key) { TranslationTreeModel model = (TranslationTreeModel) getModel(); TranslationTreeNode node = model.getNodeByKey(key); if (node == null) { TranslationTreeNode parent = (TranslationTreeNode) model.getClosestParentNodeByKey(key); String newKey = ResourceKeys.childKey(key, parent.getKey()); String firstPart = ResourceKeys.firstPart(newKey); String lastPart = ResourceKeys.withoutFirstPart(newKey); model.insertNodeInto(new TranslationTreeNode(firstPart, lastPart.isEmpty() ? Lists.newArrayList() : Lists.newArrayList(lastPart)), parent); node = model.getNodeByKey(key); } setSelectionNode(node); return node; } public void removeNodeByKey(String key) { TranslationTreeModel model = (TranslationTreeModel) getModel(); TranslationTreeNode node = model.getNodeByKey(key); if (node != null) { model.removeNodeFromParent(node); } } public TranslationTreeNode getNodeByKey(String key) { TranslationTreeModel model = (TranslationTreeModel) getModel(); return model.getNodeByKey(key); } public List<TranslationTreeNode> getExpandedNodes() { TranslationTreeNode node = (TranslationTreeNode) getModel().getRoot(); return getExpandedNodes(node); } public List<TranslationTreeNode> getExpandedNodes(TranslationTreeNode node) { List<TranslationTreeNode> expandedNodes = Lists.newLinkedList(); Enumeration<TreePath> expandedChilds = getExpandedDescendants(new TreePath(node.getPath())); if (expandedChilds != null) { while (expandedChilds.hasMoreElements()) { TreePath path = expandedChilds.nextElement(); TranslationTreeNode expandedNode = (TranslationTreeNode) path.getLastPathComponent(); if (!expandedNode.isRoot()) { // do not return the root node expandedNodes.add(expandedNode); } } } return expandedNodes; } public void renameNodeByKey(String key, String newKey) { duplicateNodeByKey(key, newKey, false); } public void duplicateNodeByKey(String key, String newKey) { duplicateNodeByKey(key, newKey, true); } public TranslationTreeNode getSelectionNode() { return (TranslationTreeNode) getLastSelectedPathComponent(); } @Override public void setSelectionPath(TreePath path) { super.setSelectionPath(path); scrollPathToVisible(path); } @Override public void setSelectionRow(int row) { TreePath path = getPathForRow(row); setSelectionPath(path); } public void setSelectionNode(TranslationTreeNode node) { TreePath path = new TreePath(node.getPath()); setSelectionPath(path); } public void clear() { setModel(new TranslationTreeModel()); } @Override protected void paintComponent(Graphics g) { TranslationTreeCellRenderer renderer = (TranslationTreeCellRenderer) getCellRenderer(); Color c1 = renderer.getSelectionBackground(); FontMetrics metrics = g.getFontMetrics(getFont()); setRowHeight(metrics.getHeight() + 8); g.setColor(getBackground()); g.fillRect(0, 0, getWidth(), getHeight()); for (int i : getSelectionRows()) { Rectangle r = getRowBounds(i); g.setColor(c1); g.fillRect(0, r.y, getWidth(), r.height); } super.paintComponent(g); } private void setupUI() { UIManager.put("Tree.repaintWholeRow", Boolean.TRUE); // Remove all key strokes InputMap inputMap = getInputMap().getParent(); for (KeyStroke k : getRegisteredKeyStrokes()) { inputMap.remove(k); } setUI(new TranslationTreeUI()); setCellRenderer(new TranslationTreeCellRenderer()); addTreeWillExpandListener(new TranslationTreeExpandListener()); addMouseListener(new TranslationTreeMouseListener()); setEditable(false); setOpaque(false); } private void duplicateNodeByKey(String key, String newKey, boolean keepOld) { TranslationTreeModel model = (TranslationTreeModel) getModel(); TranslationTreeNode node = model.getNodeByKey(key); TranslationTreeNode newNode = model.getNodeByKey(newKey); List<TranslationTreeNode> expandedNodes = null; if (keepOld) { node = node.cloneWithChildren(); } else { expandedNodes = getExpandedNodes(node); model.removeNodeFromParent(node); } if (node.isLeaf() && newNode != null) { model.removeNodeFromParent(newNode); newNode = null; } if (newNode != null) { model.insertDescendantsInto(node, newNode); node = newNode; } else { TranslationTreeNode parent = addNodeByKey(ResourceKeys.withoutLastPart(newKey)); node.setName(ResourceKeys.lastPart(newKey)); model.insertNodeInto(node, parent); } if (expandedNodes != null) { expand(expandedNodes); } setSelectionNode(node); } private class TranslationTreeMouseListener extends MouseAdapter { private boolean isPopupTrigger; @Override public void mousePressed(MouseEvent e) { isPopupTrigger = e.isPopupTrigger(); } @Override public void mouseReleased(MouseEvent e) { if (!isPopupTrigger && !e.isPopupTrigger() && e.getClickCount() == getToggleClickCount()) { int row = getRowForLocation(e.getX(), e.getY()); if (isCollapsed(row)) { expandRow(row); } else { collapseRow(row); } } } } private class TranslationTreeExpandListener implements TreeWillExpandListener { @Override public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException {} @Override public void treeWillCollapse(TreeExpansionEvent e) throws ExpandVetoException { // Prevent root key from being collapsed if (e.getPath().getPathCount() == 1) { throw new ExpandVetoException(e); } } } }