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 return Objects.hash(relation, layer); 057 } 058 059 @Override 060 public boolean equals(Object obj) { 061 if (this == obj) return true; 062 if (obj == null || getClass() != obj.getClass()) return false; 063 DialogContext that = (DialogContext) obj; 064 return Objects.equals(relation, that.relation) && 065 Objects.equals(layer, that.layer); 066 } 067 068 public boolean matchesLayer(OsmDataLayer layer) { 069 if (layer == null) return false; 070 return this.layer.equals(layer); 071 } 072 073 @Override 074 public String toString() { 075 return "[Context: layer=" + layer.getName() + ",relation=" + relation.getId() + ']'; 076 } 077 } 078 079 /** the map of open dialogs */ 080 private final Map<DialogContext, RelationEditor> openDialogs; 081 082 /** 083 * constructor 084 */ 085 public RelationDialogManager() { 086 openDialogs = new HashMap<>(); 087 } 088 089 /** 090 * Register the relation editor for a relation managed by a 091 * {@link OsmDataLayer}. 092 * 093 * @param layer the layer 094 * @param relation the relation 095 * @param editor the editor 096 */ 097 public void register(OsmDataLayer layer, Relation relation, RelationEditor editor) { 098 if (relation == null) { 099 relation = new Relation(); 100 } 101 DialogContext context = new DialogContext(layer, relation); 102 openDialogs.put(context, editor); 103 editor.addWindowListener(this); 104 } 105 106 public void updateContext(OsmDataLayer layer, Relation relation, RelationEditor editor) { 107 // lookup the entry for editor and remove it 108 // 109 for (Iterator<Entry<DialogContext, RelationEditor>> it = openDialogs.entrySet().iterator(); it.hasNext();) { 110 Entry<DialogContext, RelationEditor> entry = it.next(); 111 if (Objects.equals(entry.getValue(), editor)) { 112 it.remove(); 113 break; 114 } 115 } 116 // don't add a window listener. Editor is already known to the relation dialog manager 117 // 118 DialogContext context = new DialogContext(layer, relation); 119 openDialogs.put(context, editor); 120 } 121 122 /** 123 * Closes the editor open for a specific layer and a specific relation. 124 * 125 * @param layer the layer 126 * @param relation the relation 127 */ 128 public void close(OsmDataLayer layer, Relation relation) { 129 DialogContext context = new DialogContext(layer, relation); 130 RelationEditor editor = openDialogs.get(context); 131 if (editor != null) { 132 editor.setVisible(false); 133 } 134 } 135 136 /** 137 * Replies true if there is an open relation editor for the relation managed 138 * by the given layer. Replies false if relation is null. 139 * 140 * @param layer the layer 141 * @param relation the relation. May be null. 142 * @return true if there is an open relation editor for the relation managed 143 * by the given layer; false otherwise 144 */ 145 public boolean isOpenInEditor(OsmDataLayer layer, Relation relation) { 146 if (relation == null) return false; 147 DialogContext context = new DialogContext(layer, relation); 148 return openDialogs.keySet().contains(context); 149 150 } 151 152 /** 153 * Replies the editor for the relation managed by layer. Null, if no such editor 154 * is currently open. Returns null, if relation is null. 155 * 156 * @param layer the layer 157 * @param relation the relation 158 * @return the editor for the relation managed by layer. Null, if no such editor 159 * is currently open. 160 * 161 * @see #isOpenInEditor(OsmDataLayer, Relation) 162 */ 163 public RelationEditor getEditorForRelation(OsmDataLayer layer, Relation relation) { 164 if (relation == null) return null; 165 DialogContext context = new DialogContext(layer, relation); 166 return openDialogs.get(context); 167 } 168 169 /** 170 * called when a layer is removed 171 * 172 */ 173 @Override 174 public void layerRemoved(Layer oldLayer) { 175 if (!(oldLayer instanceof OsmDataLayer)) 176 return; 177 OsmDataLayer dataLayer = (OsmDataLayer) oldLayer; 178 179 Iterator<Entry<DialogContext, RelationEditor>> it = openDialogs.entrySet().iterator(); 180 while (it.hasNext()) { 181 Entry<DialogContext, RelationEditor> entry = it.next(); 182 if (entry.getKey().matchesLayer(dataLayer)) { 183 RelationEditor editor = entry.getValue(); 184 it.remove(); 185 editor.setVisible(false); 186 editor.dispose(); 187 } 188 } 189 } 190 191 @Override 192 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 193 // do nothing 194 } 195 196 @Override 197 public void layerAdded(Layer newLayer) { 198 // do nothing 199 } 200 201 @Override 202 public void windowClosed(WindowEvent e) { 203 RelationEditor editor = (RelationEditor) e.getWindow(); 204 for (Iterator<Entry<DialogContext, RelationEditor>> it = openDialogs.entrySet().iterator(); it.hasNext();) { 205 if (editor.equals(it.next().getValue())) { 206 it.remove(); 207 break; 208 } 209 } 210 } 211 212 /** 213 * Replies true, if there is another open {@link RelationEditor} whose 214 * upper left corner is close to <code>p</code>. 215 * 216 * @param p the reference point to check 217 * @param thisEditor the current editor 218 * @return true, if there is another open {@link RelationEditor} whose 219 * upper left corner is close to <code>p</code>. 220 */ 221 protected boolean hasEditorWithCloseUpperLeftCorner(Point p, RelationEditor thisEditor) { 222 for (RelationEditor editor: openDialogs.values()) { 223 if (editor == thisEditor) { 224 continue; 225 } 226 Point corner = editor.getLocation(); 227 if (p.x >= corner.x -5 && corner.x + 5 >= p.x 228 && p.y >= corner.y -5 && corner.y + 5 >= p.y) 229 return true; 230 } 231 return false; 232 } 233 234 /** 235 * Positions a {@link RelationEditor} on the screen. Tries to center it on the 236 * screen. If it hide another instance of an editor at the same position this 237 * method tries to reposition <code>editor</code> by moving it slightly down and 238 * slightly to the right. 239 * 240 * @param editor the editor 241 */ 242 public void positionOnScreen(RelationEditor editor) { 243 if (editor == null) return; 244 if (!openDialogs.isEmpty()) { 245 Point corner = editor.getLocation(); 246 while (hasEditorWithCloseUpperLeftCorner(corner, editor)) { 247 // shift a little, so that the dialogs are not exactly on top of each other 248 corner.x += 20; 249 corner.y += 20; 250 } 251 editor.setLocation(corner); 252 } 253 } 254 255}