001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.relation;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.beans.PropertyChangeListener;
007import java.beans.PropertyChangeSupport;
008import java.util.Collection;
009
010import org.openstreetmap.josm.data.osm.Relation;
011import org.openstreetmap.josm.data.osm.RelationMember;
012import org.openstreetmap.josm.gui.ExtendedDialog;
013import org.openstreetmap.josm.gui.MainApplication;
014import org.openstreetmap.josm.gui.layer.OsmDataLayer;
015import org.openstreetmap.josm.tools.CheckParameterUtil;
016import org.openstreetmap.josm.tools.Logging;
017
018/**
019 * Abstract relation editor.
020 * @since 1599
021 */
022public abstract class RelationEditor extends ExtendedDialog implements IRelationEditor {
023    private static final long serialVersionUID = 1L;
024
025    /** the property name for the current relation.
026     * @see #setRelation(Relation)
027     * @see #getRelation()
028     */
029    public static final String RELATION_PROP = RelationEditor.class.getName() + ".relation";
030
031    /** the property name for the current relation snapshot
032     * @see #getRelationSnapshot()
033     */
034    public static final String RELATION_SNAPSHOT_PROP = RelationEditor.class.getName() + ".relationSnapshot";
035
036    /** The relation that this editor is working on. */
037    private transient Relation relation;
038
039    /** The version of the relation when editing is started. This is null if a new relation is created. */
040    private transient Relation relationSnapshot;
041
042    /** The data layer the relation belongs to */
043    private final transient OsmDataLayer layer;
044
045    private final PropertyChangeSupport support = new PropertyChangeSupport(this);
046
047    /**
048     * Creates a new relation editor
049     *
050     * @param layer the {@link OsmDataLayer} in whose context a relation is edited. Must not be null.
051     * @param relation the relation. Can be null if a new relation is to be edited.
052     * @throws IllegalArgumentException if layer is null
053     */
054    protected RelationEditor(OsmDataLayer layer, Relation relation) {
055        super(MainApplication.getMainFrame(),
056                "",
057                new String[] {tr("Apply Changes"), tr("Cancel")},
058                false,
059                false
060        );
061        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
062        this.layer = layer;
063        setRelation(relation);
064        layer.removeRecentRelation(relation);
065    }
066
067    /**
068     * This is a factory method that creates an appropriate RelationEditor instance suitable for editing the relation
069     * that was passed in as an argument.
070     *
071     * This method is guaranteed to return a working RelationEditor.
072     *
073     * @param layer the data layer the relation is a member of
074     * @param r the relation to be edited. If the relation doesn't belong to a {@code DataSet}
075     * callers MUST NOT use the relation after calling this method.
076     * @param selectedMembers a collection of relation members which shall be selected when the editor is first launched
077     * @return an instance of RelationEditor suitable for editing that kind of relation
078     */
079    public static RelationEditor getEditor(OsmDataLayer layer, Relation r, Collection<RelationMember> selectedMembers) {
080        if (RelationDialogManager.getRelationDialogManager().isOpenInEditor(layer, r))
081            return RelationDialogManager.getRelationDialogManager().getEditorForRelation(layer, r);
082        else {
083            RelationEditor editor = new GenericRelationEditor(layer, r, selectedMembers);
084            if (r != null && r.getDataSet() == null) {
085                // see #19885: We have to assume that the relation was only created as container for tags and members
086                // the editor has created its own copy by now.
087                // Since the members point to the container we unlink them here.
088                Logging.debug("Member list is reset for relation  {0}", r.getUniqueId());
089                r.setMembers(null);
090            }
091
092            RelationDialogManager.getRelationDialogManager().positionOnScreen(editor);
093            RelationDialogManager.getRelationDialogManager().register(layer, r, editor);
094            return editor;
095        }
096    }
097
098    /**
099     * updates the title of the relation editor
100     */
101    protected void updateTitle() {
102        if (getRelation() == null) {
103            setTitle(tr("Create new relation in layer ''{0}''", layer.getName()));
104        } else if (getRelation().isNew()) {
105            setTitle(tr("Edit new relation in layer ''{0}''", layer.getName()));
106        } else {
107            setTitle(tr("Edit relation #{0} in layer ''{1}''", relation.getId(), layer.getName()));
108        }
109    }
110
111    @Override
112    public final Relation getRelation() {
113        return relation;
114    }
115
116    @Override
117    public final void setRelation(Relation relation) {
118        setRelationSnapshot((relation == null) ? null : new Relation(relation));
119        Relation oldValue = this.relation;
120        this.relation = relation;
121        if (this.relation != oldValue) {
122            support.firePropertyChange(RELATION_PROP, oldValue, this.relation);
123        }
124        updateTitle();
125    }
126
127    @Override
128    public final OsmDataLayer getLayer() {
129        return layer;
130    }
131
132    @Override
133    public final Relation getRelationSnapshot() {
134        return relationSnapshot;
135    }
136
137    protected final void setRelationSnapshot(Relation snapshot) {
138        if (relationSnapshot != null && relationSnapshot.getDataSet() == null)
139            relationSnapshot.setMembers(null); // see #19885
140
141        Relation oldValue = relationSnapshot;
142        relationSnapshot = snapshot;
143        if (relationSnapshot != oldValue) {
144            support.firePropertyChange(RELATION_SNAPSHOT_PROP, oldValue, relationSnapshot);
145        }
146    }
147
148    @Override
149    public final boolean isDirtyRelation() {
150        return !relation.hasEqualSemanticAttributes(relationSnapshot);
151    }
152
153    /* ----------------------------------------------------------------------- */
154    /* property change support                                                 */
155    /* ----------------------------------------------------------------------- */
156
157    @Override
158    public final void addPropertyChangeListener(PropertyChangeListener listener) {
159        this.support.addPropertyChangeListener(listener);
160    }
161
162    @Override
163    public final void removePropertyChangeListener(PropertyChangeListener listener) {
164        this.support.removePropertyChangeListener(listener);
165    }
166
167    @Override
168    public void dispose() {
169        layer.setRecentRelation(relation);
170        super.dispose();
171    }
172}