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}