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