001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.gui.dialogs.relation;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.beans.PropertyChangeListener;
007 import java.beans.PropertyChangeSupport;
008 import java.lang.reflect.Constructor;
009 import java.lang.reflect.Method;
010 import java.util.ArrayList;
011 import java.util.Collection;
012
013 import org.openstreetmap.josm.Main;
014 import org.openstreetmap.josm.data.osm.Relation;
015 import org.openstreetmap.josm.data.osm.RelationMember;
016 import org.openstreetmap.josm.gui.ExtendedDialog;
017 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
018 import org.openstreetmap.josm.tools.CheckParameterUtil;
019
020 public abstract class RelationEditor extends ExtendedDialog {
021 /** the property name for the current relation.
022 * @see #setRelation(Relation)
023 * @see #getRelation()
024 */
025 static public final String RELATION_PROP = RelationEditor.class.getName() + ".relation";
026
027 /** the property name for the current relation snapshot
028 * @see #getRelationSnapshot()
029 */
030 static public final String RELATION_SNAPSHOT_PROP = RelationEditor.class.getName() + ".relationSnapshot";
031
032 /** the list of registered relation editor classes */
033 private static ArrayList<Class<RelationEditor>> editors = new ArrayList<Class<RelationEditor>>();
034
035 /**
036 * Registers a relation editor class. Depending on the type of relation to be edited
037 * {@link #getEditor(OsmDataLayer, Relation, Collection)} will create an instance of
038 * this class.
039 *
040 * @param clazz the class
041 */
042 public void registerRelationEditor(Class<RelationEditor> clazz) {
043 if (clazz == null) return;
044 if (!editors.contains(clazz)) {
045 editors.add(clazz);
046 }
047 }
048
049 /**
050 * The relation that this editor is working on.
051 */
052 private Relation relation;
053
054 /**
055 * The version of the relation when editing is started. This is
056 * null if a new relation is created. */
057 private Relation relationSnapshot;
058
059 /** the data layer the relation belongs to */
060 private OsmDataLayer layer;
061
062 /**
063 * This is a factory method that creates an appropriate RelationEditor
064 * instance suitable for editing the relation that was passed in as an
065 * argument.
066 *
067 * This method is guaranteed to return a working RelationEditor. If no
068 * specific editor has been registered for the type of relation, then
069 * a generic editor will be returned.
070 *
071 * Editors can be registered by adding their class to the static list "editors"
072 * in the RelationEditor class. When it comes to editing a relation, all
073 * registered editors are queried via their static "canEdit" method whether they
074 * feel responsible for that kind of relation, and if they return true
075 * then an instance of that class will be used.
076 *
077 * @param layer the data layer the relation is a member of
078 * @param r the relation to be edited
079 * @param selectedMembers a collection of relation members which shall be selected when the
080 * editor is first launched
081 * @return an instance of RelationEditor suitable for editing that kind of relation
082 */
083 public static RelationEditor getEditor(OsmDataLayer layer, Relation r, Collection<RelationMember> selectedMembers) {
084 for (Class<RelationEditor> e : editors) {
085 try {
086 Method m = e.getMethod("canEdit", Relation.class);
087 Boolean canEdit = (Boolean) m.invoke(null, r);
088 if (canEdit) {
089 Constructor<RelationEditor> con = e.getConstructor(Relation.class, Collection.class);
090 RelationEditor editor = con.newInstance(layer, r, selectedMembers);
091 return editor;
092 }
093 } catch (Exception ex) {
094 // plod on
095 }
096 }
097 if (RelationDialogManager.getRelationDialogManager().isOpenInEditor(layer, r))
098 return RelationDialogManager.getRelationDialogManager().getEditorForRelation(layer, r);
099 else {
100 RelationEditor editor = new GenericRelationEditor(layer, r, selectedMembers);
101 RelationDialogManager.getRelationDialogManager().positionOnScreen(editor);
102 RelationDialogManager.getRelationDialogManager().register(layer, r, editor);
103 return editor;
104 }
105 }
106
107 /**
108 * Creates a new relation editor
109 *
110 * @param layer the {@link OsmDataLayer} in whose context a relation is edited. Must not be null.
111 * @param relation the relation. Can be null if a new relation is to be edited.
112 * @param selectedMembers a collection of members in <code>relation</code> which the editor
113 * should display selected when the editor is first displayed on screen
114 * @throws IllegalArgumentException thrown if layer is null
115 */
116 protected RelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) throws IllegalArgumentException{
117 // Initalizes ExtendedDialog
118 super(Main.parent,
119 "",
120 new String[] { tr("Apply Changes"), tr("Cancel")},
121 false,
122 false
123 );
124 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
125 this.layer = layer;
126 setRelation(relation);
127 }
128
129 /**
130 * updates the title of the relation editor
131 */
132 protected void updateTitle() {
133 if (getRelation() == null) {
134 setTitle(tr("Create new relation in layer ''{0}''", layer.getName()));
135 } else if (getRelation().isNew()) {
136 setTitle(tr("Edit new relation in layer ''{0}''", layer.getName()));
137 } else {
138 setTitle(tr("Edit relation #{0} in layer ''{1}''", relation.getId(), layer.getName()));
139 }
140 }
141 /**
142 * Replies the currently edited relation
143 *
144 * @return the currently edited relation
145 */
146 protected Relation getRelation() {
147 return relation;
148 }
149
150 /**
151 * Sets the currently edited relation. Creates a snapshot of the current
152 * state of the relation. See {@link #getRelationSnapshot()}
153 *
154 * @param relation the relation
155 */
156 protected void setRelation(Relation relation) {
157 setRelationSnapshot((relation == null) ? null : new Relation(relation));
158 Relation oldValue = this.relation;
159 this.relation = relation;
160 if (this.relation != oldValue) {
161 support.firePropertyChange(RELATION_PROP, oldValue, this.relation);
162 }
163 updateTitle();
164 }
165
166 /**
167 * Replies the {@link OsmDataLayer} in whose context this relation editor is
168 * open
169 *
170 * @return the {@link OsmDataLayer} in whose context this relation editor is
171 * open
172 */
173 protected OsmDataLayer getLayer() {
174 return layer;
175 }
176
177 /**
178 * Replies the state of the edited relation when the editor has been launched
179 *
180 * @return the state of the edited relation when the editor has been launched
181 */
182 protected Relation getRelationSnapshot() {
183 return relationSnapshot;
184 }
185
186 protected void setRelationSnapshot(Relation snapshot) {
187 Relation oldValue = relationSnapshot;
188 relationSnapshot = snapshot;
189 if (relationSnapshot != oldValue) {
190 support.firePropertyChange(RELATION_SNAPSHOT_PROP, oldValue, relationSnapshot);
191 }
192 }
193
194 /**
195 * Replies true if the currently edited relation has been changed elsewhere.
196 *
197 * In this case a relation editor can't apply updates to the relation directly. Rather,
198 * it has to create a conflict.
199 *
200 * @return true if the currently edited relation has been changed elsewhere.
201 */
202 protected boolean isDirtyRelation() {
203 return ! relation.hasEqualSemanticAttributes(relationSnapshot);
204 }
205
206 /* ----------------------------------------------------------------------- */
207 /* property change support */
208 /* ----------------------------------------------------------------------- */
209 final private PropertyChangeSupport support = new PropertyChangeSupport(this);
210
211 @Override
212 public void addPropertyChangeListener(PropertyChangeListener listener) {
213 this.support.addPropertyChangeListener(listener);
214 }
215
216 @Override
217 public void removePropertyChangeListener(PropertyChangeListener listener) {
218 this.support.removePropertyChangeListener(listener);
219 }
220 }