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 }