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