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