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