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