001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.io; 003 004import java.awt.BorderLayout; 005import java.util.Map; 006import java.util.Objects; 007import java.util.Optional; 008 009import javax.swing.JPanel; 010import javax.swing.event.ChangeEvent; 011import javax.swing.event.ChangeListener; 012import javax.swing.event.TableModelEvent; 013import javax.swing.event.TableModelListener; 014 015import org.openstreetmap.josm.data.osm.Changeset; 016import org.openstreetmap.josm.gui.MainApplication; 017import org.openstreetmap.josm.gui.tagging.TagEditorPanel; 018import org.openstreetmap.josm.gui.tagging.TagModel; 019import org.openstreetmap.josm.spi.preferences.Config; 020 021/** 022 * Tag settings panel of upload dialog. 023 * @since 2599 024 */ 025public class TagSettingsPanel extends JPanel implements TableModelListener { 026 027 /** checkbox for selecting whether an atomic upload is to be used */ 028 private final TagEditorPanel pnlTagEditor = new TagEditorPanel(null, null, Changeset.MAX_CHANGESET_TAG_LENGTH); 029 /** the model for the changeset comment */ 030 private final transient ChangesetCommentModel changesetCommentModel; 031 private final transient ChangesetCommentModel changesetSourceModel; 032 private final transient ChangesetReviewModel changesetReviewModel; 033 034 /** 035 * Creates a new panel 036 * 037 * @param changesetCommentModel the changeset comment model. Must not be null. 038 * @param changesetSourceModel the changeset source model. Must not be null. 039 * @param changesetReviewModel the model for the changeset review. Must not be null. 040 * @throws NullPointerException if a model is null 041 * @since 12719 (signature) 042 */ 043 public TagSettingsPanel(ChangesetCommentModel changesetCommentModel, ChangesetCommentModel changesetSourceModel, 044 ChangesetReviewModel changesetReviewModel) { 045 this.changesetCommentModel = Objects.requireNonNull(changesetCommentModel, "changesetCommentModel"); 046 this.changesetSourceModel = Objects.requireNonNull(changesetSourceModel, "changesetSourceModel"); 047 this.changesetReviewModel = Objects.requireNonNull(changesetReviewModel, "changesetReviewModel"); 048 changesetCommentModel.addChangeListener(new ChangesetCommentChangeListener("comment", "hashtags")); 049 changesetSourceModel.addChangeListener(new ChangesetCommentChangeListener("source")); 050 changesetReviewModel.addChangeListener(new ChangesetReviewChangeListener()); 051 build(); 052 pnlTagEditor.getModel().addTableModelListener(this); 053 } 054 055 protected void build() { 056 setLayout(new BorderLayout()); 057 add(pnlTagEditor, BorderLayout.CENTER); 058 } 059 060 protected void setProperty(String key, String value) { 061 String val = (value == null ? "" : value).trim(); 062 String commentInTag = getTagEditorValue(key); 063 if (val.equals(commentInTag)) 064 return; 065 066 if (val.isEmpty()) { 067 pnlTagEditor.getModel().delete(key); 068 return; 069 } 070 TagModel tag = pnlTagEditor.getModel().get(key); 071 if (tag == null) { 072 tag = new TagModel(key, val); 073 pnlTagEditor.getModel().add(tag); 074 } else { 075 pnlTagEditor.getModel().updateTagValue(tag, val); 076 } 077 } 078 079 protected String getTagEditorValue(String key) { 080 TagModel tag = pnlTagEditor.getModel().get(key); 081 return tag == null ? null : tag.getValue(); 082 } 083 084 /** 085 * Initialize panel from the given tags. 086 * @param tags the tags used to initialize the panel 087 */ 088 public void initFromTags(Map<String, String> tags) { 089 pnlTagEditor.getModel().initFromTags(tags); 090 } 091 092 /** 093 * Replies the map with the current tags in the tag editor model. 094 * @param keepEmpty {@code true} to keep empty tags 095 * @return the map with the current tags in the tag editor model. 096 */ 097 public Map<String, String> getTags(boolean keepEmpty) { 098 forceCommentFieldReload(); 099 return pnlTagEditor.getModel().getTags(keepEmpty); 100 } 101 102 /** 103 * Initializes the panel for user input 104 */ 105 public void startUserInput() { 106 pnlTagEditor.initAutoCompletion(MainApplication.getLayerManager().getEditLayer()); 107 } 108 109 /* -------------------------------------------------------------------------- */ 110 /* Interface TableChangeListener */ 111 /* -------------------------------------------------------------------------- */ 112 @Override 113 public void tableChanged(TableModelEvent e) { 114 changesetCommentModel.setComment(getTagEditorValue("comment")); 115 changesetSourceModel.setComment(getTagEditorValue("source")); 116 changesetReviewModel.setReviewRequested("yes".equals(getTagEditorValue("review_requested"))); 117 } 118 119 /** 120 * Force update the fields if the user is currently changing them. See #5676 121 */ 122 private void forceCommentFieldReload() { 123 setProperty("comment", changesetCommentModel.getComment()); 124 setProperty("source", changesetSourceModel.getComment()); 125 setProperty("review_requested", changesetReviewModel.isReviewRequested() ? "yes" : null); 126 } 127 128 /** 129 * Observes the changeset comment model and keeps the tag editor in sync 130 * with the current changeset comment 131 */ 132 class ChangesetCommentChangeListener implements ChangeListener { 133 134 private final String key; 135 private final String hashtagsKey; 136 137 ChangesetCommentChangeListener(String key) { 138 this(key, null); 139 } 140 141 ChangesetCommentChangeListener(String key, String hashtagsKey) { 142 this.key = key; 143 this.hashtagsKey = hashtagsKey; 144 } 145 146 @Override 147 public void stateChanged(ChangeEvent e) { 148 if (e.getSource() instanceof ChangesetCommentModel) { 149 ChangesetCommentModel model = ((ChangesetCommentModel) e.getSource()); 150 String newValue = model.getComment(); 151 String oldValue = Optional.ofNullable(getTagEditorValue(key)).orElse(""); 152 if (!oldValue.equals(newValue)) { 153 setProperty(key, newValue); 154 if (hashtagsKey != null && Config.getPref().getBoolean("upload.changeset.hashtags", true)) { 155 String newHashTags = String.join(";", model.findHashTags()); 156 String oldHashTags = Optional.ofNullable(getTagEditorValue(hashtagsKey)).orElse(""); 157 if (!oldHashTags.equals(newHashTags)) { 158 setProperty(hashtagsKey, newHashTags); 159 } 160 } 161 } 162 } 163 } 164 } 165 166 /** 167 * Observes the changeset review model and keeps the tag editor in sync 168 * with the current changeset review request 169 */ 170 class ChangesetReviewChangeListener implements ChangeListener { 171 172 private static final String KEY = "review_requested"; 173 174 @Override 175 public void stateChanged(ChangeEvent e) { 176 if (e.getSource() instanceof ChangesetReviewModel) { 177 boolean newState = ((ChangesetReviewModel) e.getSource()).isReviewRequested(); 178 boolean oldState = "yes".equals(Optional.ofNullable(getTagEditorValue(KEY)).orElse("")); 179 if (oldState != newState) { 180 setProperty(KEY, newState ? "yes" : null); 181 } 182 } 183 } 184 } 185}