001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.relation; 003 004import java.awt.Point; 005import java.awt.event.WindowAdapter; 006import java.awt.event.WindowEvent; 007import java.util.HashMap; 008import java.util.Iterator; 009import java.util.Map; 010import java.util.Map.Entry; 011import java.util.Objects; 012 013import org.openstreetmap.josm.data.osm.Relation; 014import org.openstreetmap.josm.gui.MapView; 015import org.openstreetmap.josm.gui.layer.Layer; 016import org.openstreetmap.josm.gui.layer.OsmDataLayer; 017 018/** 019 * RelationDialogManager keeps track of the open relation editors. 020 * 021 */ 022public class RelationDialogManager extends WindowAdapter implements MapView.LayerChangeListener { 023 024 /** keeps track of open relation editors */ 025 private static RelationDialogManager relationDialogManager; 026 027 /** 028 * Replies the singleton {@link RelationDialogManager} 029 * 030 * @return the singleton {@link RelationDialogManager} 031 */ 032 public static RelationDialogManager getRelationDialogManager() { 033 if (RelationDialogManager.relationDialogManager == null) { 034 RelationDialogManager.relationDialogManager = new RelationDialogManager(); 035 MapView.addLayerChangeListener(RelationDialogManager.relationDialogManager); 036 } 037 return RelationDialogManager.relationDialogManager; 038 } 039 040 /** 041 * Helper class for keeping the context of a relation editor. A relation editor 042 * is open for a specific relation managed by a specific {@link OsmDataLayer} 043 * 044 */ 045 private static class DialogContext { 046 public final Relation relation; 047 public final OsmDataLayer layer; 048 049 DialogContext(OsmDataLayer layer, Relation relation) { 050 this.layer = layer; 051 this.relation = relation; 052 } 053 054 @Override 055 public int hashCode() { 056 final int prime = 31; 057 int result = 1; 058 result = prime * result + ((layer == null) ? 0 : layer.hashCode()); 059 result = prime * result + ((relation == null) ? 0 : relation.hashCode()); 060 return result; 061 } 062 063 @Override 064 public boolean equals(Object obj) { 065 if (this == obj) 066 return true; 067 if (obj == null) 068 return false; 069 if (getClass() != obj.getClass()) 070 return false; 071 DialogContext other = (DialogContext) obj; 072 if (layer == null) { 073 if (other.layer != null) 074 return false; 075 } else if (!layer.equals(other.layer)) 076 return false; 077 if (relation == null) { 078 if (other.relation != null) 079 return false; 080 } else if (!relation.equals(other.relation)) 081 return false; 082 return true; 083 } 084 085 public boolean matchesLayer(OsmDataLayer layer) { 086 if (layer == null) return false; 087 return this.layer.equals(layer); 088 } 089 090 @Override 091 public String toString() { 092 return "[Context: layer=" + layer.getName() + ",relation=" + relation.getId() + ']'; 093 } 094 } 095 096 /** the map of open dialogs */ 097 private final Map<DialogContext, RelationEditor> openDialogs; 098 099 /** 100 * constructor 101 */ 102 public RelationDialogManager() { 103 openDialogs = new HashMap<>(); 104 } 105 106 /** 107 * Register the relation editor for a relation managed by a 108 * {@link OsmDataLayer}. 109 * 110 * @param layer the layer 111 * @param relation the relation 112 * @param editor the editor 113 */ 114 public void register(OsmDataLayer layer, Relation relation, RelationEditor editor) { 115 if (relation == null) { 116 relation = new Relation(); 117 } 118 DialogContext context = new DialogContext(layer, relation); 119 openDialogs.put(context, editor); 120 editor.addWindowListener(this); 121 } 122 123 public void updateContext(OsmDataLayer layer, Relation relation, RelationEditor editor) { 124 // lookup the entry for editor and remove it 125 // 126 for (Iterator<Entry<DialogContext, RelationEditor>> it = openDialogs.entrySet().iterator(); it.hasNext();) { 127 Entry<DialogContext, RelationEditor> entry = it.next(); 128 if (Objects.equals(entry.getValue(), editor)) { 129 it.remove(); 130 break; 131 } 132 } 133 // don't add a window listener. Editor is already known to the relation dialog manager 134 // 135 DialogContext context = new DialogContext(layer, relation); 136 openDialogs.put(context, editor); 137 } 138 139 /** 140 * Closes the editor open for a specific layer and a specific relation. 141 * 142 * @param layer the layer 143 * @param relation the relation 144 */ 145 public void close(OsmDataLayer layer, Relation relation) { 146 DialogContext context = new DialogContext(layer, relation); 147 RelationEditor editor = openDialogs.get(context); 148 if (editor != null) { 149 editor.setVisible(false); 150 } 151 } 152 153 /** 154 * Replies true if there is an open relation editor for the relation managed 155 * by the given layer. Replies false if relation is null. 156 * 157 * @param layer the layer 158 * @param relation the relation. May be null. 159 * @return true if there is an open relation editor for the relation managed 160 * by the given layer; false otherwise 161 */ 162 public boolean isOpenInEditor(OsmDataLayer layer, Relation relation) { 163 if (relation == null) return false; 164 DialogContext context = new DialogContext(layer, relation); 165 return openDialogs.keySet().contains(context); 166 167 } 168 169 /** 170 * Replies the editor for the relation managed by layer. Null, if no such editor 171 * is currently open. Returns null, if relation is null. 172 * 173 * @param layer the layer 174 * @param relation the relation 175 * @return the editor for the relation managed by layer. Null, if no such editor 176 * is currently open. 177 * 178 * @see #isOpenInEditor(OsmDataLayer, Relation) 179 */ 180 public RelationEditor getEditorForRelation(OsmDataLayer layer, Relation relation) { 181 if (relation == null) return null; 182 DialogContext context = new DialogContext(layer, relation); 183 return openDialogs.get(context); 184 } 185 186 /** 187 * called when a layer is removed 188 * 189 */ 190 @Override 191 public void layerRemoved(Layer oldLayer) { 192 if (!(oldLayer instanceof OsmDataLayer)) 193 return; 194 OsmDataLayer dataLayer = (OsmDataLayer) oldLayer; 195 196 Iterator<Entry<DialogContext, RelationEditor>> it = openDialogs.entrySet().iterator(); 197 while (it.hasNext()) { 198 Entry<DialogContext, RelationEditor> entry = it.next(); 199 if (entry.getKey().matchesLayer(dataLayer)) { 200 RelationEditor editor = entry.getValue(); 201 it.remove(); 202 editor.setVisible(false); 203 editor.dispose(); 204 } 205 } 206 } 207 208 @Override 209 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 210 // do nothing 211 } 212 213 @Override 214 public void layerAdded(Layer newLayer) { 215 // do nothing 216 } 217 218 @Override 219 public void windowClosed(WindowEvent e) { 220 RelationEditor editor = (RelationEditor) e.getWindow(); 221 for (Iterator<Entry<DialogContext, RelationEditor>> it = openDialogs.entrySet().iterator(); it.hasNext();) { 222 if (editor.equals(it.next().getValue())) { 223 it.remove(); 224 break; 225 } 226 } 227 } 228 229 /** 230 * Replies true, if there is another open {@link RelationEditor} whose 231 * upper left corner is close to <code>p</code>. 232 * 233 * @param p the reference point to check 234 * @param thisEditor the current editor 235 * @return true, if there is another open {@link RelationEditor} whose 236 * upper left corner is close to <code>p</code>. 237 */ 238 protected boolean hasEditorWithCloseUpperLeftCorner(Point p, RelationEditor thisEditor) { 239 for (RelationEditor editor: openDialogs.values()) { 240 if (editor == thisEditor) { 241 continue; 242 } 243 Point corner = editor.getLocation(); 244 if (p.x >= corner.x -5 && corner.x + 5 >= p.x 245 && p.y >= corner.y -5 && corner.y + 5 >= p.y) 246 return true; 247 } 248 return false; 249 } 250 251 /** 252 * Positions a {@link RelationEditor} on the screen. Tries to center it on the 253 * screen. If it hide another instance of an editor at the same position this 254 * method tries to reposition <code>editor</code> by moving it slightly down and 255 * slightly to the right. 256 * 257 * @param editor the editor 258 */ 259 public void positionOnScreen(RelationEditor editor) { 260 if (editor == null) return; 261 if (!openDialogs.isEmpty()) { 262 Point corner = editor.getLocation(); 263 while (hasEditorWithCloseUpperLeftCorner(corner, editor)) { 264 // shift a little, so that the dialogs are not exactly on top of each other 265 corner.x += 20; 266 corner.y += 20; 267 } 268 editor.setLocation(corner); 269 } 270 } 271 272}