001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.BorderLayout; 007import java.awt.GridBagLayout; 008import java.awt.event.ActionEvent; 009import java.awt.event.ActionListener; 010import java.awt.event.FocusAdapter; 011import java.awt.event.FocusEvent; 012import java.awt.event.ItemEvent; 013import java.awt.event.KeyAdapter; 014import java.awt.event.KeyEvent; 015import java.util.Arrays; 016import java.util.Collections; 017import java.util.LinkedList; 018import java.util.List; 019import java.util.concurrent.TimeUnit; 020 021import javax.swing.Action; 022import javax.swing.BorderFactory; 023import javax.swing.JCheckBox; 024import javax.swing.JEditorPane; 025import javax.swing.JPanel; 026import javax.swing.event.ChangeEvent; 027import javax.swing.event.ChangeListener; 028import javax.swing.event.HyperlinkEvent; 029 030import org.openstreetmap.josm.data.osm.Changeset; 031import org.openstreetmap.josm.gui.MainApplication; 032import org.openstreetmap.josm.gui.widgets.HistoryComboBox; 033import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 034import org.openstreetmap.josm.spi.preferences.Config; 035import org.openstreetmap.josm.tools.CheckParameterUtil; 036import org.openstreetmap.josm.tools.GBC; 037import org.openstreetmap.josm.tools.Utils; 038 039/** 040 * BasicUploadSettingsPanel allows to enter the basic parameters required for uploading data. 041 * @since 2599 042 */ 043public class BasicUploadSettingsPanel extends JPanel { 044 /** 045 * Preference name for history collection 046 */ 047 public static final String HISTORY_KEY = "upload.comment.history"; 048 /** 049 * Preference name for last used upload comment 050 */ 051 public static final String HISTORY_LAST_USED_KEY = "upload.comment.last-used"; 052 /** 053 * Preference name for the max age search comments may have 054 */ 055 public static final String HISTORY_MAX_AGE_KEY = "upload.comment.max-age"; 056 /** 057 * Preference name for the history of source values 058 */ 059 public static final String SOURCE_HISTORY_KEY = "upload.source.history"; 060 061 /** the history combo box for the upload comment */ 062 private final HistoryComboBox hcbUploadComment = new HistoryComboBox(); 063 private final HistoryComboBox hcbUploadSource = new HistoryComboBox(); 064 /** the panel with a summary of the upload parameters */ 065 private final UploadParameterSummaryPanel pnlUploadParameterSummary = new UploadParameterSummaryPanel(); 066 /** the checkbox to request feedback from other users */ 067 private final JCheckBox cbRequestReview = new JCheckBox(tr("I would like someone to review my edits.")); 068 /** the changeset comment model */ 069 private final transient ChangesetCommentModel changesetCommentModel; 070 private final transient ChangesetCommentModel changesetSourceModel; 071 private final transient ChangesetReviewModel changesetReviewModel; 072 073 protected JPanel buildUploadCommentPanel() { 074 JPanel pnl = new JPanel(new GridBagLayout()); 075 076 JEditorPane commentLabel = new JMultilineLabel("<html><b>" + tr("Provide a brief comment for the changes you are uploading:")); 077 pnl.add(commentLabel, GBC.eol().insets(0, 5, 10, 3).fill(GBC.HORIZONTAL)); 078 hcbUploadComment.setToolTipText(tr("Enter an upload comment")); 079 hcbUploadComment.setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH); 080 List<String> cmtHistory = new LinkedList<>(Config.getPref().getList(HISTORY_KEY, new LinkedList<String>())); 081 Collections.reverse(cmtHistory); // we have to reverse the history, because ComboBoxHistory will reverse it again in addElement() 082 hcbUploadComment.setPossibleItems(cmtHistory); 083 CommentModelListener commentModelListener = new CommentModelListener(hcbUploadComment, changesetCommentModel); 084 hcbUploadComment.getEditor().addActionListener(commentModelListener); 085 hcbUploadComment.getEditorComponent().addFocusListener(commentModelListener); 086 pnl.add(hcbUploadComment, GBC.eol().fill(GBC.HORIZONTAL)); 087 088 JEditorPane sourceLabel = new JMultilineLabel("<html><b>" + tr("Specify the data source for the changes") 089 + "</b> (<a href=\"urn:changeset-source\">" + tr("obtain from current layers") + "</a>)<b>:</b>"); 090 sourceLabel.addHyperlinkListener(e -> { 091 if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) { 092 final String source = MainApplication.getMap().mapView.getLayerInformationForSourceTag(); 093 hcbUploadSource.setText(Utils.shortenString(source, Changeset.MAX_CHANGESET_TAG_LENGTH)); 094 // Fix #9965 095 changesetSourceModel.setComment(hcbUploadSource.getText()); 096 } 097 }); 098 pnl.add(sourceLabel, GBC.eol().insets(0, 8, 10, 3).fill(GBC.HORIZONTAL)); 099 100 hcbUploadSource.setToolTipText(tr("Enter a source")); 101 hcbUploadSource.setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH); 102 List<String> sourceHistory = new LinkedList<>(Config.getPref().getList(SOURCE_HISTORY_KEY, getDefaultSources())); 103 Collections.reverse(sourceHistory); // we have to reverse the history, because ComboBoxHistory will reverse it again in addElement() 104 hcbUploadSource.setPossibleItems(sourceHistory); 105 CommentModelListener sourceModelListener = new CommentModelListener(hcbUploadSource, changesetSourceModel); 106 hcbUploadSource.getEditor().addActionListener(sourceModelListener); 107 hcbUploadSource.getEditorComponent().addFocusListener(sourceModelListener); 108 pnl.add(hcbUploadSource, GBC.eol().fill(GBC.HORIZONTAL)); 109 return pnl; 110 } 111 112 /** 113 * Returns the default list of sources. 114 * @return the default list of sources 115 */ 116 public static List<String> getDefaultSources() { 117 return Arrays.asList("knowledge", "survey", "Bing"); 118 } 119 120 protected void build() { 121 setLayout(new BorderLayout()); 122 setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); 123 add(buildUploadCommentPanel(), BorderLayout.NORTH); 124 add(pnlUploadParameterSummary, BorderLayout.CENTER); 125 add(cbRequestReview, BorderLayout.SOUTH); 126 cbRequestReview.addItemListener(e -> changesetReviewModel.setReviewRequested(e.getStateChange() == ItemEvent.SELECTED)); 127 } 128 129 /** 130 * Creates the panel 131 * 132 * @param changesetCommentModel the model for the changeset comment. Must not be null 133 * @param changesetSourceModel the model for the changeset source. Must not be null. 134 * @param changesetReviewModel the model for the changeset review. Must not be null. 135 * @throws IllegalArgumentException if {@code changesetCommentModel} is null 136 * @since 12719 (signature) 137 */ 138 public BasicUploadSettingsPanel(ChangesetCommentModel changesetCommentModel, ChangesetCommentModel changesetSourceModel, 139 ChangesetReviewModel changesetReviewModel) { 140 CheckParameterUtil.ensureParameterNotNull(changesetCommentModel, "changesetCommentModel"); 141 CheckParameterUtil.ensureParameterNotNull(changesetSourceModel, "changesetSourceModel"); 142 CheckParameterUtil.ensureParameterNotNull(changesetReviewModel, "changesetReviewModel"); 143 this.changesetCommentModel = changesetCommentModel; 144 this.changesetSourceModel = changesetSourceModel; 145 this.changesetReviewModel = changesetReviewModel; 146 changesetCommentModel.addChangeListener(new ChangesetCommentChangeListener(hcbUploadComment)); 147 changesetSourceModel.addChangeListener(new ChangesetCommentChangeListener(hcbUploadSource)); 148 changesetReviewModel.addChangeListener(new ChangesetReviewChangeListener()); 149 build(); 150 } 151 152 public void setUploadTagDownFocusTraversalHandlers(final Action handler) { 153 setHistoryComboBoxDownFocusTraversalHandler(handler, hcbUploadComment); 154 setHistoryComboBoxDownFocusTraversalHandler(handler, hcbUploadSource); 155 } 156 157 public void setHistoryComboBoxDownFocusTraversalHandler(final Action handler, final HistoryComboBox hcb) { 158 hcb.getEditor().addActionListener(handler); 159 hcb.getEditorComponent().addKeyListener(new HistoryComboBoxKeyAdapter(hcb, handler)); 160 } 161 162 /** 163 * Remembers the user input in the preference settings 164 */ 165 public void rememberUserInput() { 166 // store the history of comments 167 hcbUploadComment.addCurrentItemToHistory(); 168 Config.getPref().putList(HISTORY_KEY, hcbUploadComment.getHistory()); 169 Config.getPref().putInt(HISTORY_LAST_USED_KEY, (int) (TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))); 170 // store the history of sources 171 hcbUploadSource.addCurrentItemToHistory(); 172 Config.getPref().putList(SOURCE_HISTORY_KEY, hcbUploadSource.getHistory()); 173 } 174 175 /** 176 * Initializes the panel for user input 177 */ 178 public void startUserInput() { 179 hcbUploadComment.requestFocusInWindow(); 180 hcbUploadComment.getEditorComponent().requestFocusInWindow(); 181 } 182 183 /** 184 * Initializes editing of upload comment. 185 */ 186 public void initEditingOfUploadComment() { 187 hcbUploadComment.getEditor().selectAll(); 188 hcbUploadComment.requestFocusInWindow(); 189 } 190 191 /** 192 * Initializes editing of upload source. 193 */ 194 public void initEditingOfUploadSource() { 195 hcbUploadSource.getEditor().selectAll(); 196 hcbUploadSource.requestFocusInWindow(); 197 } 198 199 public UploadParameterSummaryPanel getUploadParameterSummaryPanel() { 200 return pnlUploadParameterSummary; 201 } 202 203 static final class HistoryComboBoxKeyAdapter extends KeyAdapter { 204 private final HistoryComboBox hcb; 205 private final Action handler; 206 207 HistoryComboBoxKeyAdapter(HistoryComboBox hcb, Action handler) { 208 this.hcb = hcb; 209 this.handler = handler; 210 } 211 212 @Override 213 public void keyTyped(KeyEvent e) { 214 if (e.getKeyCode() == KeyEvent.VK_TAB) { 215 handler.actionPerformed(new ActionEvent(hcb, 0, "focusDown")); 216 } 217 } 218 } 219 220 /** 221 * Updates the changeset comment model upon changes in the input field. 222 */ 223 static class CommentModelListener extends FocusAdapter implements ActionListener { 224 225 private final HistoryComboBox source; 226 private final ChangesetCommentModel destination; 227 228 CommentModelListener(HistoryComboBox source, ChangesetCommentModel destination) { 229 this.source = source; 230 this.destination = destination; 231 } 232 233 @Override 234 public void actionPerformed(ActionEvent e) { 235 destination.setComment(source.getText()); 236 } 237 238 @Override 239 public void focusLost(FocusEvent e) { 240 destination.setComment(source.getText()); 241 } 242 } 243 244 /** 245 * Observes the changeset comment model and keeps the comment input field 246 * in sync with the current changeset comment 247 */ 248 static class ChangesetCommentChangeListener implements ChangeListener { 249 250 private final HistoryComboBox destination; 251 252 ChangesetCommentChangeListener(HistoryComboBox destination) { 253 this.destination = destination; 254 } 255 256 @Override 257 public void stateChanged(ChangeEvent e) { 258 if (!(e.getSource() instanceof ChangesetCommentModel)) return; 259 String newComment = ((ChangesetCommentModel) e.getSource()).getComment(); 260 if (!destination.getText().equals(newComment)) { 261 destination.setText(newComment); 262 } 263 } 264 } 265 266 /** 267 * Observes the changeset review model and keeps the review checkbox 268 * in sync with the current changeset review request 269 */ 270 class ChangesetReviewChangeListener implements ChangeListener { 271 @Override 272 public void stateChanged(ChangeEvent e) { 273 if (!(e.getSource() instanceof ChangesetReviewModel)) return; 274 boolean newState = ((ChangesetReviewModel) e.getSource()).isReviewRequested(); 275 if (cbRequestReview.isSelected() != newState) { 276 cbRequestReview.setSelected(newState); 277 } 278 } 279 } 280}