| 1 | /* |
| 2 | * This program is free software: you can redistribute it and/or modify |
| 3 | * it under the terms of the GNU Lesser General Public License as published |
| 4 | * by the Free Software Foundation, either version 3 of the License, or |
| 5 | * (at your option) any later version. |
| 6 | * |
| 7 | * This program is distributed in the hope that it will be useful, |
| 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | * GNU Lesser General Public License for more details. |
| 11 | * |
| 12 | * You should have received a copy of the GNU Lesser General Public License |
| 13 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 14 | */ |
| 15 | // Can be found at: https://code.google.com/archive/p/aephyr/source/default/source |
| 16 | // package aephyr.swing; |
| 17 | package be.nikiroo.utils.ui; |
| 18 | |
| 19 | import java.awt.*; |
| 20 | import java.awt.event.*; |
| 21 | |
| 22 | import javax.swing.*; |
| 23 | import javax.swing.tree.*; |
| 24 | |
| 25 | import java.util.*; |
| 26 | |
| 27 | public class TreeCellSpanner extends Container implements TreeCellRenderer, ComponentListener { |
| 28 | |
| 29 | public TreeCellSpanner(JTree tree, TreeCellRenderer renderer) { |
| 30 | if (tree == null || renderer == null) |
| 31 | throw new NullPointerException(); |
| 32 | this.tree = tree; |
| 33 | this.renderer = renderer; |
| 34 | treeParent = tree.getParent(); |
| 35 | if (treeParent != null && treeParent instanceof JViewport) { |
| 36 | treeParent.addComponentListener(this); |
| 37 | } else { |
| 38 | treeParent = null; |
| 39 | tree.addComponentListener(this); |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | protected final JTree tree; |
| 44 | |
| 45 | private TreeCellRenderer renderer; |
| 46 | |
| 47 | private Component rendererComponent; |
| 48 | |
| 49 | private Container treeParent; |
| 50 | |
| 51 | private Map<TreePath,Integer> offsets = new HashMap<TreePath,Integer>(); |
| 52 | |
| 53 | private TreePath path; |
| 54 | |
| 55 | public TreeCellRenderer getRenderer() { |
| 56 | return renderer; |
| 57 | } |
| 58 | |
| 59 | @Override |
| 60 | public Component getTreeCellRendererComponent(JTree tree, Object value, |
| 61 | boolean selected, boolean expanded, boolean leaf, int row, |
| 62 | boolean hasFocus) { |
| 63 | path = tree.getPathForRow(row); |
| 64 | if (path != null && path.getLastPathComponent() != value) |
| 65 | path = null; |
| 66 | rendererComponent = renderer.getTreeCellRendererComponent( |
| 67 | tree, value, selected, expanded, leaf, row, hasFocus); |
| 68 | if (getComponentCount() < 1 || getComponent(0) != rendererComponent) { |
| 69 | removeAll(); |
| 70 | add(rendererComponent); |
| 71 | } |
| 72 | return this; |
| 73 | } |
| 74 | |
| 75 | @Override |
| 76 | public void doLayout() { |
| 77 | int x = getX(); |
| 78 | if (x < 0) |
| 79 | return; |
| 80 | if (path != null) { |
| 81 | Integer offset = offsets.get(path); |
| 82 | if (offset == null || offset.intValue() != x) { |
| 83 | offsets.put(path, x); |
| 84 | fireTreePathChanged(path); |
| 85 | } |
| 86 | } |
| 87 | rendererComponent.setBounds(getX(), getY(), getWidth(), getHeight()); |
| 88 | } |
| 89 | |
| 90 | @Override |
| 91 | public void paint(Graphics g) { |
| 92 | if (rendererComponent != null) |
| 93 | rendererComponent.paint(g); |
| 94 | } |
| 95 | |
| 96 | @Override |
| 97 | public Dimension getPreferredSize() { |
| 98 | Dimension s = rendererComponent.getPreferredSize(); |
| 99 | // check if path count is greater than 1 to exclude the root |
| 100 | if (path != null && path.getPathCount() > 1) { |
| 101 | Integer offset = offsets.get(path); |
| 102 | if (offset != null) { |
| 103 | int width; |
| 104 | if (tree.getParent() == treeParent) { |
| 105 | width = treeParent.getWidth(); |
| 106 | } else { |
| 107 | if (treeParent != null) { |
| 108 | treeParent.removeComponentListener(this); |
| 109 | tree.addComponentListener(this); |
| 110 | treeParent = null; |
| 111 | } |
| 112 | if (tree.getParent() instanceof JViewport) { |
| 113 | treeParent = tree.getParent(); |
| 114 | tree.removeComponentListener(this); |
| 115 | treeParent.addComponentListener(this); |
| 116 | width = treeParent.getWidth(); |
| 117 | } else { |
| 118 | width = tree.getWidth(); |
| 119 | } |
| 120 | } |
| 121 | s.width = width - offset; |
| 122 | } |
| 123 | } |
| 124 | return s; |
| 125 | } |
| 126 | |
| 127 | |
| 128 | protected void fireTreePathChanged(TreePath path) { |
| 129 | if (path.getPathCount() > 1) { |
| 130 | // this cannot be used for the root node or else |
| 131 | // the entire tree will keep being revalidated ad infinitum |
| 132 | TreeModel model = tree.getModel(); |
| 133 | Object node = path.getLastPathComponent(); |
| 134 | if (node instanceof TreeNode && (model instanceof DefaultTreeModel |
| 135 | || (model instanceof TreeModelTransformer<?> && |
| 136 | (model=((TreeModelTransformer<?>)model).getModel()) instanceof DefaultTreeModel))) { |
| 137 | ((DefaultTreeModel)model).nodeChanged((TreeNode)node); |
| 138 | } else { |
| 139 | model.valueForPathChanged(path, node.toString()); |
| 140 | } |
| 141 | } else { |
| 142 | // root! |
| 143 | |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | |
| 148 | private int lastWidth; |
| 149 | |
| 150 | @Override |
| 151 | public void componentHidden(ComponentEvent e) {} |
| 152 | |
| 153 | @Override |
| 154 | public void componentMoved(ComponentEvent e) {} |
| 155 | |
| 156 | @Override |
| 157 | public void componentResized(ComponentEvent e) { |
| 158 | if (e.getComponent().getWidth() != lastWidth) { |
| 159 | lastWidth = e.getComponent().getWidth(); |
| 160 | for (int row=tree.getRowCount(); --row>=0;) { |
| 161 | fireTreePathChanged(tree.getPathForRow(row)); |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | @Override |
| 167 | public void componentShown(ComponentEvent e) {} |
| 168 | |
| 169 | } |