source: josm/trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java@ 18173

Last change on this file since 18173 was 18173, checked in by Don-vip, 3 years ago

fix #20690 - fix #21240 - Refactoring of UploadDialog, HistoryComboBox and AutoCompletingComboBox (patch by marcello)

  • Property svn:eol-style set to native
File size: 16.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.GridBagLayout;
8import java.awt.event.ActionEvent;
9import java.awt.event.ActionListener;
10import java.awt.event.FocusEvent;
11import java.awt.event.FocusListener;
12import java.awt.event.ItemEvent;
13import java.awt.event.ItemListener;
14import java.awt.event.KeyEvent;
15import java.awt.event.KeyListener;
16import java.util.ArrayList;
17import java.util.Arrays;
18import java.util.Collection;
19import java.util.List;
20import java.util.Map;
21import java.util.Optional;
22import java.util.concurrent.TimeUnit;
23
24import javax.swing.BorderFactory;
25import javax.swing.JCheckBox;
26import javax.swing.JEditorPane;
27import javax.swing.JLabel;
28import javax.swing.JPanel;
29import javax.swing.JTextField;
30import javax.swing.event.HyperlinkEvent;
31import javax.swing.event.TableModelEvent;
32import javax.swing.event.TableModelListener;
33
34import org.openstreetmap.josm.data.osm.Changeset;
35import org.openstreetmap.josm.data.osm.OsmPrimitive;
36import org.openstreetmap.josm.gui.MainApplication;
37import org.openstreetmap.josm.gui.io.UploadTextComponentValidator.UploadAreaValidator;
38import org.openstreetmap.josm.gui.io.UploadTextComponentValidator.UploadCommentValidator;
39import org.openstreetmap.josm.gui.io.UploadTextComponentValidator.UploadSourceValidator;
40import org.openstreetmap.josm.gui.tagging.TagModel;
41import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
42import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
43import org.openstreetmap.josm.spi.preferences.Config;
44import org.openstreetmap.josm.tools.GBC;
45import org.openstreetmap.josm.tools.Utils;
46
47/**
48 * BasicUploadSettingsPanel allows to enter the basic parameters required for uploading data.
49 * @since 2599
50 */
51public class BasicUploadSettingsPanel extends JPanel implements ActionListener, FocusListener, ItemListener, KeyListener, TableModelListener {
52 /**
53 * Preference name for the history of comments
54 */
55 public static final String COMMENT_HISTORY_KEY = "upload.comment.history";
56 /**
57 * Preference name for last used upload comment
58 */
59 public static final String COMMENT_LAST_USED_KEY = "upload.comment.last-used";
60 /**
61 * Preference name for the max age search comments may have
62 */
63 public static final String COMMENT_MAX_AGE_KEY = "upload.comment.max-age";
64 /**
65 * Preference name for the history of sources
66 */
67 public static final String SOURCE_HISTORY_KEY = "upload.source.history";
68
69 /** the history combo box for the upload comment */
70 private final HistoryComboBox hcbUploadComment = new HistoryComboBox();
71 private final HistoryComboBox hcbUploadSource = new HistoryComboBox();
72 private final transient JCheckBox obtainSourceAutomatically = new JCheckBox(
73 tr("Automatically obtain source from current layers"));
74 /** the panel with a summary of the upload parameters */
75 private final UploadParameterSummaryPanel pnlUploadParameterSummary = new UploadParameterSummaryPanel();
76 /** the checkbox to request feedback from other users */
77 private final JCheckBox cbRequestReview = new JCheckBox(tr("I would like someone to review my edits."));
78 private final JLabel areaValidatorFeedback = new JLabel();
79 private final UploadAreaValidator areaValidator = new UploadAreaValidator(new JTextField(), areaValidatorFeedback);
80 /** the changeset comment model */
81 private final transient UploadDialogModel model;
82 private final transient JLabel uploadCommentFeedback = new JLabel();
83 private final transient UploadCommentValidator uploadCommentValidator = new UploadCommentValidator(
84 hcbUploadComment.getEditorComponent(), uploadCommentFeedback);
85 private final transient JLabel hcbUploadSourceFeedback = new JLabel();
86 private final transient UploadSourceValidator uploadSourceValidator = new UploadSourceValidator(
87 hcbUploadSource.getEditorComponent(), hcbUploadSourceFeedback);
88
89 /** a lock to prevent loops in notifications */
90 private boolean locked;
91
92 /**
93 * Creates the panel
94 *
95 * @param model The tag editor model.
96 *
97 * @since 18173 (signature)
98 */
99 public BasicUploadSettingsPanel(UploadDialogModel model) {
100 this.model = model;
101 this.model.addTableModelListener(this);
102 build();
103 }
104
105 protected JPanel buildUploadCommentPanel() {
106 JPanel pnl = new JPanel(new GridBagLayout());
107 pnl.setBorder(BorderFactory.createTitledBorder(tr("Provide a brief comment for the changes you are uploading:")));
108
109 hcbUploadComment.setToolTipText(tr("Enter an upload comment"));
110 hcbUploadComment.setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
111 JTextField editor = hcbUploadComment.getEditorComponent();
112 editor.getDocument().putProperty("tag", "comment");
113 editor.addKeyListener(this);
114 editor.addFocusListener(this);
115 editor.addActionListener(this);
116 pnl.add(hcbUploadComment, GBC.eol().fill(GBC.HORIZONTAL));
117 pnl.add(uploadCommentFeedback, GBC.eol().insets(0, 3, 0, 0).fill(GBC.HORIZONTAL));
118 return pnl;
119 }
120
121 protected JPanel buildUploadSourcePanel() {
122 JPanel pnl = new JPanel(new GridBagLayout());
123 pnl.setBorder(BorderFactory.createTitledBorder(tr("Specify the data source for the changes")));
124
125 JEditorPane obtainSourceOnce = new JMultilineLabel(
126 "<html>(<a href=\"urn:changeset-source\">" + tr("just once") + "</a>)</html>");
127 obtainSourceOnce.addHyperlinkListener(e -> {
128 if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) {
129 saveEdits();
130 model.put("source", getSourceFromLayer());
131 }
132 });
133 obtainSourceAutomatically.setSelected(Config.getPref().getBoolean("upload.source.obtainautomatically", false));
134 obtainSourceAutomatically.addActionListener(e -> {
135 if (obtainSourceAutomatically.isSelected()) {
136 model.put("source", getSourceFromLayer());
137 }
138 obtainSourceOnce.setVisible(!obtainSourceAutomatically.isSelected());
139 });
140 JPanel obtainSource = new JPanel(new GridBagLayout());
141 obtainSource.add(obtainSourceAutomatically, GBC.std().anchor(GBC.WEST));
142 obtainSource.add(obtainSourceOnce, GBC.std().anchor(GBC.WEST));
143 obtainSource.add(new JLabel(), GBC.eol().fill(GBC.HORIZONTAL));
144 if (Config.getPref().getBoolean("upload.show.automatic.source", true)) {
145 pnl.add(obtainSource, GBC.eol().insets(0, 0, 10, 3).fill(GBC.HORIZONTAL));
146 }
147
148 hcbUploadSource.setToolTipText(tr("Enter a source"));
149 hcbUploadSource.setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
150 JTextField editor = hcbUploadSource.getEditorComponent();
151 editor.getDocument().putProperty("tag", "source");
152 editor.addKeyListener(this);
153 editor.addFocusListener(this);
154 editor.addActionListener(this);
155 pnl.add(hcbUploadSource, GBC.eol().fill(GBC.HORIZONTAL));
156 pnl.add(hcbUploadSourceFeedback, GBC.eol().insets(0, 3, 0, 0).fill(GBC.HORIZONTAL));
157
158 return pnl;
159 }
160
161 /**
162 * Initializes this life cycle of the panel.
163 *
164 * Adds any changeset tags to the map.
165 *
166 * @param map Map where tags are added to.
167 * @since 18173
168 */
169 public void initLifeCycle(Map<String, String> map) {
170 Optional.ofNullable(getLastChangesetTagFromHistory(COMMENT_HISTORY_KEY, new ArrayList<>())).ifPresent(
171 x -> map.put("comment", x));
172 Optional.ofNullable(getLastChangesetTagFromHistory(SOURCE_HISTORY_KEY, getDefaultSources())).ifPresent(
173 x -> map.put("source", x));
174 if (obtainSourceAutomatically.isSelected()) {
175 map.put("source", getSourceFromLayer());
176 }
177 hcbUploadComment.getModel().prefs().load(COMMENT_HISTORY_KEY);
178 hcbUploadComment.discardAllUndoableEdits();
179 hcbUploadSource.getModel().prefs().load(SOURCE_HISTORY_KEY, getDefaultSources());
180 hcbUploadSource.discardAllUndoableEdits();
181 }
182
183 /**
184 * Get a key's value from the model.
185 * @param key The key
186 * @return The value or ""
187 * @since 18173
188 */
189 private String get(String key) {
190 TagModel tm = model.get(key);
191 return tm == null ? "" : tm.getValue();
192 }
193
194 /**
195 * Get the topmost item from the history if not expired.
196 *
197 * @param historyKey The preferences key.
198 * @param def A default history.
199 * @return The history item (may be null).
200 * @since 18173 (signature)
201 */
202 public static String getLastChangesetTagFromHistory(String historyKey, List<String> def) {
203 Collection<String> history = Config.getPref().getList(historyKey, def);
204 long age = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) - getHistoryLastUsedKey();
205 if (age < getHistoryMaxAgeKey() && !history.isEmpty()) {
206 return history.iterator().next();
207 }
208 return null;
209 }
210
211 /**
212 * Add the "source" tag
213 * @return The source from the layer info.
214 */
215 private String getSourceFromLayer() {
216 String source = MainApplication.getMap().mapView.getLayerInformationForSourceTag();
217 return Utils.shortenString(source, Changeset.MAX_CHANGESET_TAG_LENGTH);
218 }
219
220 /**
221 * Returns the default list of sources.
222 * @return the default list of sources
223 */
224 public static List<String> getDefaultSources() {
225 return Arrays.asList("knowledge", "survey", "Bing");
226 }
227
228 /**
229 * Returns the list of {@link UploadTextComponentValidator} defined by this panel.
230 * @return the list of {@code UploadTextComponentValidator} defined by this panel.
231 * @since 17238
232 */
233 protected List<UploadTextComponentValidator> getUploadTextValidators() {
234 return Arrays.asList(areaValidator, uploadCommentValidator, uploadSourceValidator);
235 }
236
237 protected void build() {
238 setLayout(new GridBagLayout());
239 setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
240 GBC gbc = GBC.eol().insets(0, 0, 0, 20).fill(GBC.HORIZONTAL);
241 add(buildUploadCommentPanel(), gbc);
242 add(buildUploadSourcePanel(), gbc);
243 add(pnlUploadParameterSummary, gbc);
244 if (Config.getPref().getBoolean("upload.show.review.request", true)) {
245 add(cbRequestReview, gbc);
246 cbRequestReview.addItemListener(this);
247 }
248 add(areaValidatorFeedback, gbc);
249 add(new JPanel(), GBC.std().fill(GBC.BOTH));
250 }
251
252 /**
253 * Remembers the user input in the preference settings
254 */
255 public void rememberUserInput() {
256 // store the history of comments
257 if (getHistoryMaxAgeKey() > 0) {
258 hcbUploadComment.addCurrentItemToHistory();
259 hcbUploadComment.getModel().prefs().save(COMMENT_HISTORY_KEY);
260 Config.getPref().putLong(COMMENT_LAST_USED_KEY, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
261 }
262 // store the history of sources
263 hcbUploadSource.addCurrentItemToHistory();
264 hcbUploadSource.getModel().prefs().save(SOURCE_HISTORY_KEY);
265
266 // store current value of obtaining source automatically
267 Config.getPref().putBoolean("upload.source.obtainautomatically", obtainSourceAutomatically.isSelected());
268 }
269
270 /**
271 * Initializes the panel for user input
272 */
273 public void startUserInput() {
274 hcbUploadComment.getEditorComponent().requestFocusInWindow();
275 uploadCommentValidator.validate();
276 uploadSourceValidator.validate();
277 }
278
279 /**
280 * Initializes editing of upload comment.
281 */
282 public void initEditingOfUploadComment() {
283 hcbUploadComment.getEditor().selectAll();
284 hcbUploadComment.requestFocusInWindow();
285 }
286
287 /**
288 * Initializes editing of upload source.
289 */
290 public void initEditingOfUploadSource() {
291 hcbUploadSource.getEditor().selectAll();
292 hcbUploadSource.requestFocusInWindow();
293 }
294
295 void setUploadedPrimitives(List<OsmPrimitive> primitives) {
296 areaValidator.computeArea(primitives);
297 }
298
299 /**
300 * Returns the panel that displays a summary of data the user is about to upload.
301 * @return the upload parameter summary panel
302 */
303 public UploadParameterSummaryPanel getUploadParameterSummaryPanel() {
304 return pnlUploadParameterSummary;
305 }
306
307 static long getHistoryMaxAgeKey() {
308 return Config.getPref().getLong(COMMENT_MAX_AGE_KEY, TimeUnit.HOURS.toSeconds(4));
309 }
310
311 static long getHistoryLastUsedKey() {
312 return Config.getPref().getLong(COMMENT_LAST_USED_KEY, 0);
313 }
314
315 /**
316 * Updates the combobox histories when a combobox editor loses focus.
317 *
318 * @param text The {@code JTextField} of the combobox editor.
319 */
320 private void updateHistory(JTextField text) {
321 String tag = (String) text.getDocument().getProperty("tag"); // tag is either "comment" or "source"
322 if (tag.equals("comment")) {
323 hcbUploadComment.addCurrentItemToHistory();
324 }
325 if (tag.equals("source")) {
326 hcbUploadSource.addCurrentItemToHistory();
327 }
328 }
329
330 /**
331 * Updates the table editor model with changes in the comboboxes.
332 *
333 * The lock prevents loops in change notifications, eg. the combobox
334 * notifies the table model and the table model notifies the combobox, which
335 * throws IllegalStateException.
336 *
337 * @param text The {@code JTextField} of the combobox editor.
338 */
339 private void updateModel(JTextField text) {
340 if (!locked) {
341 locked = true;
342 try {
343 String tag = (String) text.getDocument().getProperty("tag"); // tag is either "comment" or "source"
344 String value = text.getText();
345 model.put(tag, value.isEmpty() ? null : value); // remove tags with empty values
346 } finally {
347 locked = false;
348 }
349 }
350 }
351
352 /**
353 * Save all outstanding edits to the model.
354 * @see UploadDialog#saveEdits
355 * @since 18173
356 */
357 public void saveEdits() {
358 updateModel(hcbUploadComment.getEditorComponent());
359 hcbUploadComment.addCurrentItemToHistory();
360 updateModel(hcbUploadSource.getEditorComponent());
361 hcbUploadSource.addCurrentItemToHistory();
362 }
363
364 /**
365 * Returns the UplodDialog that is our ancestor
366 *
367 * @return the UploadDialog or null
368 */
369 private UploadDialog getDialog() {
370 Component d = getRootPane();
371 while ((d = d.getParent()) != null) {
372 if (d instanceof UploadDialog)
373 return (UploadDialog) d;
374 }
375 return null;
376 }
377
378 /**
379 * Update the model when the selection changes in a combobox.
380 * @param e The action event.
381 */
382 @Override
383 public void actionPerformed(ActionEvent e) {
384 getDialog().setFocusToUploadButton();
385 }
386
387 @Override
388 public void focusGained(FocusEvent e) {
389 }
390
391 /**
392 * Update the model and combobox history when a combobox editor loses focus.
393 */
394 @Override
395 public void focusLost(FocusEvent e) {
396 Object c = e.getSource();
397 if (c instanceof JTextField) {
398 updateModel((JTextField) c);
399 updateHistory((JTextField) c);
400 }
401 }
402
403 /**
404 * Updates the table editor model upon changes in the "review" checkbox.
405 */
406 @Override
407 public void itemStateChanged(ItemEvent e) {
408 if (!locked) {
409 locked = true;
410 try {
411 model.put("review_requested", e.getStateChange() == ItemEvent.SELECTED ? "yes" : null);
412 } finally {
413 locked = false;
414 }
415 }
416 }
417
418 /**
419 * Updates the controls upon changes in the table editor model.
420 */
421 @Override
422 public void tableChanged(TableModelEvent e) {
423 if (!locked) {
424 locked = true;
425 try {
426 hcbUploadComment.setText(get("comment"));
427 hcbUploadSource.setText(get("source"));
428 cbRequestReview.setSelected(get("review_requested").equals("yes"));
429 } finally {
430 locked = false;
431 }
432 }
433 }
434
435 /**
436 * Set the focus directly to the upload button if "Enter" key is pressed in any combobox.
437 */
438 @Override
439 public void keyTyped(KeyEvent e) {
440 if (e.getKeyChar() == KeyEvent.VK_ENTER) {
441 getDialog().setFocusToUploadButton();
442 }
443 }
444
445 @Override
446 public void keyPressed(KeyEvent e) {
447 }
448
449 @Override
450 public void keyReleased(KeyEvent e) {
451 }
452}
Note: See TracBrowser for help on using the repository browser.