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