001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.relation; 003 004import java.util.List; 005import java.util.concurrent.CopyOnWriteArrayList; 006 007import javax.swing.event.TreeModelEvent; 008import javax.swing.event.TreeModelListener; 009import javax.swing.tree.TreeModel; 010import javax.swing.tree.TreePath; 011 012import org.openstreetmap.josm.data.osm.Relation; 013import org.openstreetmap.josm.data.osm.RelationMember; 014 015/** 016 * This is a {@link TreeModel} which provides the hierarchical structure of {@link Relation}s 017 * to a {@link javax.swing.JTree}. 018 * 019 * The model is initialized with a root relation or with a list of {@link RelationMember}s, see 020 * {@link #populate(Relation)} and {@link #populate(List)} respectively. 021 * 022 * @since 1828 023 */ 024public class RelationTreeModel implements TreeModel { 025 /** the root relation */ 026 private Relation root; 027 028 /** the tree model listeners */ 029 private final CopyOnWriteArrayList<TreeModelListener> listeners; 030 031 /** 032 * constructor 033 */ 034 public RelationTreeModel() { 035 this.root = null; 036 listeners = new CopyOnWriteArrayList<>(); 037 } 038 039 /** 040 * Replies the number of children of type relation for a particular 041 * relation <code>parent</code> 042 * 043 * @param parent the parent relation 044 * @return the number of children of type relation 045 */ 046 protected int getNumRelationChildren(Relation parent) { 047 if (parent == null) return 0; 048 int count = 0; 049 for (RelationMember member : parent.getMembers()) { 050 if (member.isRelation()) { 051 count++; 052 } 053 } 054 return count; 055 } 056 057 /** 058 * Replies the i-th child of type relation for a particular relation 059 * <code>parent</code>. 060 * 061 * @param parent the parent relation 062 * @param idx the index 063 * @return the i-th child of type relation for a particular relation 064 * <code>parent</code>; null, if no such child exists 065 */ 066 protected Relation getRelationChildByIdx(Relation parent, int idx) { 067 if (parent == null) return null; 068 int count = 0; 069 for (RelationMember member : parent.getMembers()) { 070 if (!(member.isRelation())) { 071 continue; 072 } 073 if (count == idx) 074 return member.getRelation(); 075 count++; 076 } 077 return null; 078 } 079 080 /** 081 * Replies the index of a particular <code>child</code> with respect to its 082 * <code>parent</code>. 083 * 084 * @param parent the parent relation 085 * @param child the child relation 086 * @return the index of a particular <code>child</code> with respect to its 087 * <code>parent</code>; -1 if either parent or child are null or if <code>child</code> 088 * isn't a child of <code>parent</code>. 089 * 090 */ 091 protected int getIndexForRelationChild(Relation parent, Relation child) { 092 if (parent == null || child == null) return -1; 093 int idx = 0; 094 for (RelationMember member : parent.getMembers()) { 095 if (!(member.isRelation())) { 096 continue; 097 } 098 if (member.getMember() == child) return idx; 099 idx++; 100 } 101 return -1; 102 } 103 104 /** 105 * Populates the model with a root relation 106 * 107 * @param root the root relation 108 * @see #populate(List) 109 * 110 */ 111 public void populate(Relation root) { 112 if (root == null) { 113 root = new Relation(); 114 } 115 this.root = root; 116 fireRootReplacedEvent(); 117 } 118 119 /** 120 * Populates the model with a list of relation members 121 * 122 * @param members the relation members 123 */ 124 public void populate(List<RelationMember> members) { 125 if (members == null) return; 126 Relation r = new Relation(); 127 r.setMembers(members); 128 this.root = r; 129 fireRootReplacedEvent(); 130 } 131 132 /** 133 * Notifies tree model listeners about a replacement of the 134 * root. 135 */ 136 protected void fireRootReplacedEvent() { 137 TreeModelEvent e = new TreeModelEvent(this, new TreePath(root)); 138 for (TreeModelListener l : listeners) { 139 l.treeStructureChanged(e); 140 } 141 } 142 143 /** 144 * Notifies tree model listeners about an update of the 145 * trees nodes. 146 * 147 * @param path the tree path to the node 148 */ 149 protected void fireRefreshNode(TreePath path) { 150 TreeModelEvent e = new TreeModelEvent(this, path); 151 for (TreeModelListener l : listeners) { 152 l.treeStructureChanged(e); 153 } 154 155 } 156 157 /** 158 * Invoke to notify all listeners about an update of a particular node 159 * 160 * @param pathToNode the tree path to the node 161 */ 162 public void refreshNode(TreePath pathToNode) { 163 fireRefreshNode(pathToNode); 164 } 165 166 /* ----------------------------------------------------------------------- */ 167 /* interface TreeModel */ 168 /* ----------------------------------------------------------------------- */ 169 @Override 170 public Object getChild(Object parent, int index) { 171 return getRelationChildByIdx((Relation) parent, index); 172 } 173 174 @Override 175 public int getChildCount(Object parent) { 176 return getNumRelationChildren((Relation) parent); 177 } 178 179 @Override 180 public int getIndexOfChild(Object parent, Object child) { 181 return getIndexForRelationChild((Relation) parent, (Relation) child); 182 } 183 184 @Override 185 public Object getRoot() { 186 return root; 187 } 188 189 @Override 190 public boolean isLeaf(Object node) { 191 Relation r = (Relation) node; 192 if (r.isIncomplete()) return false; 193 return getNumRelationChildren(r) == 0; 194 } 195 196 @Override 197 public void addTreeModelListener(TreeModelListener l) { 198 if (l != null) { 199 listeners.addIfAbsent(l); 200 } 201 } 202 203 @Override 204 public void removeTreeModelListener(TreeModelListener l) { 205 listeners.remove(l); 206 } 207 208 @Override 209 public void valueForPathChanged(TreePath path, Object newValue) { 210 // do nothing 211 } 212}