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