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    }