source: josm/trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java @ 12719

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

fix #15232 - Allow user to request feedback when uploading by adding the review_requested=yes changeset tag.
Inspired by:

  • Property svn:eol-style set to native
File size: 28.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.io;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.Component;
9import java.awt.Dimension;
10import java.awt.FlowLayout;
11import java.awt.GraphicsEnvironment;
12import java.awt.GridBagLayout;
13import java.awt.event.ActionEvent;
14import java.awt.event.InputEvent;
15import java.awt.event.KeyEvent;
16import java.awt.event.WindowAdapter;
17import java.awt.event.WindowEvent;
18import java.beans.PropertyChangeEvent;
19import java.beans.PropertyChangeListener;
20import java.lang.Character.UnicodeBlock;
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.HashMap;
25import java.util.Iterator;
26import java.util.List;
27import java.util.Map;
28import java.util.Map.Entry;
29import java.util.Optional;
30import java.util.concurrent.TimeUnit;
31
32import javax.swing.AbstractAction;
33import javax.swing.Action;
34import javax.swing.BorderFactory;
35import javax.swing.Icon;
36import javax.swing.JButton;
37import javax.swing.JComponent;
38import javax.swing.JOptionPane;
39import javax.swing.JPanel;
40import javax.swing.JTabbedPane;
41import javax.swing.KeyStroke;
42
43import org.openstreetmap.josm.Main;
44import org.openstreetmap.josm.data.APIDataSet;
45import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
46import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
47import org.openstreetmap.josm.data.Version;
48import org.openstreetmap.josm.data.osm.Changeset;
49import org.openstreetmap.josm.data.osm.DataSet;
50import org.openstreetmap.josm.data.osm.OsmPrimitive;
51import org.openstreetmap.josm.data.preferences.Setting;
52import org.openstreetmap.josm.gui.ExtendedDialog;
53import org.openstreetmap.josm.gui.HelpAwareOptionPane;
54import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
55import org.openstreetmap.josm.gui.help.HelpUtil;
56import org.openstreetmap.josm.gui.util.GuiHelper;
57import org.openstreetmap.josm.gui.util.WindowGeometry;
58import org.openstreetmap.josm.io.OsmApi;
59import org.openstreetmap.josm.io.UploadStrategy;
60import org.openstreetmap.josm.io.UploadStrategySpecification;
61import org.openstreetmap.josm.tools.GBC;
62import org.openstreetmap.josm.tools.ImageOverlay;
63import org.openstreetmap.josm.tools.ImageProvider;
64import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
65import org.openstreetmap.josm.tools.InputMapUtils;
66import org.openstreetmap.josm.tools.MultiLineFlowLayout;
67import org.openstreetmap.josm.tools.Utils;
68
69/**
70 * This is a dialog for entering upload options like the parameters for
71 * the upload changeset and the strategy for opening/closing a changeset.
72 * @since 2025
73 */
74public class UploadDialog extends AbstractUploadDialog implements PropertyChangeListener, PreferenceChangedListener {
75    /** the unique instance of the upload dialog */
76    private static UploadDialog uploadDialog;
77
78    /** list of custom components that can be added by plugins at JOSM startup */
79    private static final Collection<Component> customComponents = new ArrayList<>();
80
81    /** the "created_by" changeset OSM key */
82    private static final String CREATED_BY = "created_by";
83
84    /** the panel with the objects to upload */
85    private UploadedObjectsSummaryPanel pnlUploadedObjects;
86    /** the panel to select the changeset used */
87    private ChangesetManagementPanel pnlChangesetManagement;
88
89    private BasicUploadSettingsPanel pnlBasicUploadSettings;
90
91    private UploadStrategySelectionPanel pnlUploadStrategySelectionPanel;
92
93    /** checkbox for selecting whether an atomic upload is to be used  */
94    private TagSettingsPanel pnlTagSettings;
95    /** the tabbed pane used below of the list of primitives  */
96    private JTabbedPane tpConfigPanels;
97    /** the upload button */
98    private JButton btnUpload;
99
100    /** the changeset comment model keeping the state of the changeset comment */
101    private final transient ChangesetCommentModel changesetCommentModel = new ChangesetCommentModel();
102    private final transient ChangesetCommentModel changesetSourceModel = new ChangesetCommentModel();
103    private final transient ChangesetReviewModel changesetReviewModel = new ChangesetReviewModel();
104
105    private transient DataSet dataSet;
106
107    /**
108     * Constructs a new {@code UploadDialog}.
109     */
110    public UploadDialog() {
111        super(GuiHelper.getFrameForComponent(Main.parent), ModalityType.DOCUMENT_MODAL);
112        build();
113    }
114
115    /**
116     * Replies the unique instance of the upload dialog
117     *
118     * @return the unique instance of the upload dialog
119     */
120    public static synchronized UploadDialog getUploadDialog() {
121        if (uploadDialog == null) {
122            uploadDialog = new UploadDialog();
123        }
124        return uploadDialog;
125    }
126
127    /**
128     * builds the content panel for the upload dialog
129     *
130     * @return the content panel
131     */
132    protected JPanel buildContentPanel() {
133        JPanel pnl = new JPanel(new GridBagLayout());
134        pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
135
136        // the panel with the list of uploaded objects
137        pnlUploadedObjects = new UploadedObjectsSummaryPanel();
138        pnl.add(pnlUploadedObjects, GBC.eol().fill(GBC.BOTH));
139
140        // Custom components
141        for (Component c : customComponents) {
142            pnl.add(c, GBC.eol().fill(GBC.HORIZONTAL));
143        }
144
145        // a tabbed pane with configuration panels in the lower half
146        tpConfigPanels = new CompactTabbedPane();
147
148        pnlBasicUploadSettings = new BasicUploadSettingsPanel(changesetCommentModel, changesetSourceModel, changesetReviewModel);
149        tpConfigPanels.add(pnlBasicUploadSettings);
150        tpConfigPanels.setTitleAt(0, tr("Settings"));
151        tpConfigPanels.setToolTipTextAt(0, tr("Decide how to upload the data and which changeset to use"));
152
153        pnlTagSettings = new TagSettingsPanel(changesetCommentModel, changesetSourceModel, changesetReviewModel);
154        tpConfigPanels.add(pnlTagSettings);
155        tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
156        tpConfigPanels.setToolTipTextAt(1, tr("Apply tags to the changeset data is uploaded to"));
157
158        pnlChangesetManagement = new ChangesetManagementPanel(changesetCommentModel);
159        tpConfigPanels.add(pnlChangesetManagement);
160        tpConfigPanels.setTitleAt(2, tr("Changesets"));
161        tpConfigPanels.setToolTipTextAt(2, tr("Manage open changesets and select a changeset to upload to"));
162
163        pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel();
164        tpConfigPanels.add(pnlUploadStrategySelectionPanel);
165        tpConfigPanels.setTitleAt(3, tr("Advanced"));
166        tpConfigPanels.setToolTipTextAt(3, tr("Configure advanced settings"));
167
168        pnl.add(tpConfigPanels, GBC.eol().fill(GBC.HORIZONTAL));
169
170        pnl.add(buildActionPanel(), GBC.eol().fill(GBC.HORIZONTAL));
171        return pnl;
172    }
173
174    /**
175     * builds the panel with the OK and CANCEL buttons
176     *
177     * @return The panel with the OK and CANCEL buttons
178     */
179    protected JPanel buildActionPanel() {
180        JPanel pnl = new JPanel(new MultiLineFlowLayout(FlowLayout.CENTER));
181        pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
182
183        // -- upload button
184        btnUpload = new JButton(new UploadAction(this));
185        pnl.add(btnUpload);
186        btnUpload.setFocusable(true);
187        InputMapUtils.enableEnter(btnUpload);
188        bindCtrlEnterToAction(getRootPane(), btnUpload.getAction());
189
190        // -- cancel button
191        CancelAction cancelAction = new CancelAction(this);
192        pnl.add(new JButton(cancelAction));
193        InputMapUtils.addEscapeAction(getRootPane(), cancelAction);
194        pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Dialog/Upload"))));
195        HelpUtil.setHelpContext(getRootPane(), ht("/Dialog/Upload"));
196        return pnl;
197    }
198
199    /**
200     * builds the gui
201     */
202    protected void build() {
203        setTitle(tr("Upload to ''{0}''", OsmApi.getOsmApi().getBaseUrl()));
204        setContentPane(buildContentPanel());
205
206        addWindowListener(new WindowEventHandler());
207
208
209        // make sure the configuration panels listen to each other
210        // changes
211        //
212        pnlChangesetManagement.addPropertyChangeListener(this);
213        pnlChangesetManagement.addPropertyChangeListener(
214                pnlBasicUploadSettings.getUploadParameterSummaryPanel()
215        );
216        pnlChangesetManagement.addPropertyChangeListener(this);
217        pnlUploadedObjects.addPropertyChangeListener(
218                pnlBasicUploadSettings.getUploadParameterSummaryPanel()
219        );
220        pnlUploadedObjects.addPropertyChangeListener(pnlUploadStrategySelectionPanel);
221        pnlUploadStrategySelectionPanel.addPropertyChangeListener(
222                pnlBasicUploadSettings.getUploadParameterSummaryPanel()
223        );
224
225        // users can click on either of two links in the upload parameter
226        // summary handler. This installs the handler for these two events.
227        // We simply select the appropriate tab in the tabbed pane with the configuration dialogs.
228        //
229        pnlBasicUploadSettings.getUploadParameterSummaryPanel().setConfigurationParameterRequestListener(
230                new ConfigurationParameterRequestHandler() {
231                    @Override
232                    public void handleUploadStrategyConfigurationRequest() {
233                        tpConfigPanels.setSelectedIndex(3);
234                    }
235
236                    @Override
237                    public void handleChangesetConfigurationRequest() {
238                        tpConfigPanels.setSelectedIndex(2);
239                    }
240                }
241        );
242
243        pnlBasicUploadSettings.setUploadTagDownFocusTraversalHandlers(
244                new AbstractAction() {
245                    @Override
246                    public void actionPerformed(ActionEvent e) {
247                        btnUpload.requestFocusInWindow();
248                    }
249                }
250        );
251
252        setMinimumSize(new Dimension(600, 350));
253
254        Main.pref.addPreferenceChangeListener(this);
255    }
256
257    /**
258     * Sets the collection of primitives to upload
259     *
260     * @param toUpload the dataset with the objects to upload. If null, assumes the empty
261     * set of objects to upload
262     *
263     */
264    public void setUploadedPrimitives(APIDataSet toUpload) {
265        if (toUpload == null) {
266            List<OsmPrimitive> emptyList = Collections.emptyList();
267            pnlUploadedObjects.setUploadedPrimitives(emptyList, emptyList, emptyList);
268            return;
269        }
270        pnlUploadedObjects.setUploadedPrimitives(
271                toUpload.getPrimitivesToAdd(),
272                toUpload.getPrimitivesToUpdate(),
273                toUpload.getPrimitivesToDelete()
274        );
275    }
276
277    /**
278     * Sets the tags for this upload based on (later items overwrite earlier ones):
279     * <ul>
280     * <li>previous "source" and "comment" input</li>
281     * <li>the tags set in the dataset (see {@link DataSet#getChangeSetTags()})</li>
282     * <li>the tags from the selected open changeset</li>
283     * <li>the JOSM user agent (see {@link Version#getAgentString(boolean)})</li>
284     * </ul>
285     *
286     * @param dataSet to obtain the tags set in the dataset
287     */
288    public void setChangesetTags(DataSet dataSet) {
289        final Map<String, String> tags = new HashMap<>();
290
291        // obtain from previous input
292        tags.put("source", getLastChangesetSourceFromHistory());
293        tags.put("comment", getLastChangesetCommentFromHistory());
294
295        // obtain from dataset
296        if (dataSet != null) {
297            tags.putAll(dataSet.getChangeSetTags());
298        }
299        this.dataSet = dataSet;
300
301        // obtain from selected open changeset
302        if (pnlChangesetManagement.getSelectedChangeset() != null) {
303            tags.putAll(pnlChangesetManagement.getSelectedChangeset().getKeys());
304        }
305
306        // set/adapt created_by
307        final String agent = Version.getInstance().getAgentString(false);
308        final String createdBy = tags.get(CREATED_BY);
309        if (createdBy == null || createdBy.isEmpty()) {
310            tags.put(CREATED_BY, agent);
311        } else if (!createdBy.contains(agent)) {
312            tags.put(CREATED_BY, createdBy + ';' + agent);
313        }
314
315        // remove empty values
316        final Iterator<String> it = tags.keySet().iterator();
317        while (it.hasNext()) {
318            final String v = tags.get(it.next());
319            if (v == null || v.isEmpty()) {
320                it.remove();
321            }
322        }
323
324        pnlTagSettings.initFromTags(tags);
325        pnlTagSettings.tableChanged(null);
326    }
327
328    @Override
329    public void rememberUserInput() {
330        pnlBasicUploadSettings.rememberUserInput();
331        pnlUploadStrategySelectionPanel.rememberUserInput();
332    }
333
334    /**
335     * Initializes the panel for user input
336     */
337    public void startUserInput() {
338        tpConfigPanels.setSelectedIndex(0);
339        pnlBasicUploadSettings.startUserInput();
340        pnlTagSettings.startUserInput();
341        pnlUploadStrategySelectionPanel.initFromPreferences();
342        UploadParameterSummaryPanel pnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
343        pnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification());
344        pnl.setCloseChangesetAfterNextUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
345        pnl.setNumObjects(pnlUploadedObjects.getNumObjectsToUpload());
346    }
347
348    /**
349     * Replies the current changeset
350     *
351     * @return the current changeset
352     */
353    public Changeset getChangeset() {
354        Changeset cs = Optional.ofNullable(pnlChangesetManagement.getSelectedChangeset()).orElseGet(Changeset::new);
355        cs.setKeys(pnlTagSettings.getTags(false));
356        return cs;
357    }
358
359    /**
360     * Sets the changeset to be used in the next upload
361     *
362     * @param cs the changeset
363     */
364    public void setSelectedChangesetForNextUpload(Changeset cs) {
365        pnlChangesetManagement.setSelectedChangesetForNextUpload(cs);
366    }
367
368    @Override
369    public UploadStrategySpecification getUploadStrategySpecification() {
370        UploadStrategySpecification spec = pnlUploadStrategySelectionPanel.getUploadStrategySpecification();
371        spec.setCloseChangesetAfterUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
372        return spec;
373    }
374
375    @Override
376    public String getUploadComment() {
377        return changesetCommentModel.getComment();
378    }
379
380    @Override
381    public String getUploadSource() {
382        return changesetSourceModel.getComment();
383    }
384
385    @Override
386    public void setVisible(boolean visible) {
387        if (visible) {
388            new WindowGeometry(
389                    getClass().getName() + ".geometry",
390                    WindowGeometry.centerInWindow(
391                            Main.parent,
392                            new Dimension(400, 600)
393                    )
394            ).applySafe(this);
395            startUserInput();
396        } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
397            new WindowGeometry(this).remember(getClass().getName() + ".geometry");
398        }
399        super.setVisible(visible);
400    }
401
402    /**
403     * Adds a custom component to this dialog.
404     * Custom components added at JOSM startup are displayed between the objects list and the properties tab pane.
405     * @param c The custom component to add. If {@code null}, this method does nothing.
406     * @return {@code true} if the collection of custom components changed as a result of the call
407     * @since 5842
408     */
409    public static boolean addCustomComponent(Component c) {
410        if (c != null) {
411            return customComponents.add(c);
412        }
413        return false;
414    }
415
416    static final class CompactTabbedPane extends JTabbedPane {
417        @Override
418        public Dimension getPreferredSize() {
419            // make sure the tabbed pane never grabs more space than necessary
420            return super.getMinimumSize();
421        }
422    }
423
424    /**
425     * Handles an upload.
426     */
427    static class UploadAction extends AbstractAction {
428
429        private final transient IUploadDialog dialog;
430
431        UploadAction(IUploadDialog dialog) {
432            this.dialog = dialog;
433            putValue(NAME, tr("Upload Changes"));
434            putValue(SMALL_ICON, ImageProvider.get("upload"));
435            putValue(SHORT_DESCRIPTION, tr("Upload the changed primitives"));
436        }
437
438        /**
439         * Displays a warning message indicating that the upload comment is empty/short.
440         * @return true if the user wants to revisit, false if they want to continue
441         */
442        protected boolean warnUploadComment() {
443            return warnUploadTag(
444                    tr("Please revise upload comment"),
445                    tr("Your upload comment is <i>empty</i>, or <i>very short</i>.<br /><br />" +
446                            "This is technically allowed, but please consider that many users who are<br />" +
447                            "watching changes in their area depend on meaningful changeset comments<br />" +
448                            "to understand what is going on!<br /><br />" +
449                            "If you spend a minute now to explain your change, you will make life<br />" +
450                            "easier for many other mappers."),
451                    "upload_comment_is_empty_or_very_short"
452            );
453        }
454
455        /**
456         * Displays a warning message indicating that no changeset source is given.
457         * @return true if the user wants to revisit, false if they want to continue
458         */
459        protected boolean warnUploadSource() {
460            return warnUploadTag(
461                    tr("Please specify a changeset source"),
462                    tr("You did not specify a source for your changes.<br />" +
463                            "It is technically allowed, but this information helps<br />" +
464                            "other users to understand the origins of the data.<br /><br />" +
465                            "If you spend a minute now to explain your change, you will make life<br />" +
466                            "easier for many other mappers."),
467                    "upload_source_is_empty"
468            );
469        }
470
471        protected boolean warnUploadTag(final String title, final String message, final String togglePref) {
472            String[] buttonTexts = new String[] {tr("Revise"), tr("Cancel"), tr("Continue as is")};
473            Icon[] buttonIcons = new Icon[] {
474                    new ImageProvider("ok").setMaxSize(ImageSizes.LARGEICON).get(),
475                    new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get(),
476                    new ImageProvider("upload").setMaxSize(ImageSizes.LARGEICON).addOverlay(
477                            new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)).get()};
478            String[] tooltips = new String[] {
479                    tr("Return to the previous dialog to enter a more descriptive comment"),
480                    tr("Cancel and return to the previous dialog"),
481                    tr("Ignore this hint and upload anyway")};
482
483            if (GraphicsEnvironment.isHeadless()) {
484                return false;
485            }
486
487            ExtendedDialog dlg = new ExtendedDialog((Component) dialog, title, buttonTexts) {
488                @Override
489                public void setupDialog() {
490                    super.setupDialog();
491                    bindCtrlEnterToAction(getRootPane(), buttons.get(buttons.size() - 1).getAction());
492                }
493            };
494            dlg.setContent("<html>" + message + "</html>");
495            dlg.setButtonIcons(buttonIcons);
496            dlg.setToolTipTexts(tooltips);
497            dlg.setIcon(JOptionPane.WARNING_MESSAGE);
498            dlg.toggleEnable(togglePref);
499            dlg.setCancelButton(1, 2);
500            return dlg.showDialog().getValue() != 3;
501        }
502
503        protected void warnIllegalChunkSize() {
504            HelpAwareOptionPane.showOptionDialog(
505                    (Component) dialog,
506                    tr("Please enter a valid chunk size first"),
507                    tr("Illegal chunk size"),
508                    JOptionPane.ERROR_MESSAGE,
509                    ht("/Dialog/Upload#IllegalChunkSize")
510            );
511        }
512
513        static boolean isUploadCommentTooShort(String comment) {
514            String s = comment.trim();
515            boolean result = true;
516            if (!s.isEmpty()) {
517                UnicodeBlock block = Character.UnicodeBlock.of(s.charAt(0));
518                if (block != null && block.toString().contains("CJK")) {
519                    result = s.length() < 4;
520                } else {
521                    result = s.length() < 10;
522                }
523            }
524            return result;
525        }
526
527        @Override
528        public void actionPerformed(ActionEvent e) {
529            if (isUploadCommentTooShort(dialog.getUploadComment()) && warnUploadComment()) {
530                // abort for missing comment
531                dialog.handleMissingComment();
532                return;
533            }
534            if (dialog.getUploadSource().trim().isEmpty() && warnUploadSource()) {
535                // abort for missing changeset source
536                dialog.handleMissingSource();
537                return;
538            }
539
540            /* test for empty tags in the changeset metadata and proceed only after user's confirmation.
541             * though, accept if key and value are empty (cf. xor). */
542            List<String> emptyChangesetTags = new ArrayList<>();
543            for (final Entry<String, String> i : dialog.getTags(true).entrySet()) {
544                final boolean isKeyEmpty = i.getKey() == null || i.getKey().trim().isEmpty();
545                final boolean isValueEmpty = i.getValue() == null || i.getValue().trim().isEmpty();
546                final boolean ignoreKey = "comment".equals(i.getKey()) || "source".equals(i.getKey());
547                if ((isKeyEmpty ^ isValueEmpty) && !ignoreKey) {
548                    emptyChangesetTags.add(tr("{0}={1}", i.getKey(), i.getValue()));
549                }
550            }
551            if (!emptyChangesetTags.isEmpty() && JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog(
552                    Main.parent,
553                    trn(
554                            "<html>The following changeset tag contains an empty key/value:<br>{0}<br>Continue?</html>",
555                            "<html>The following changeset tags contain an empty key/value:<br>{0}<br>Continue?</html>",
556                            emptyChangesetTags.size(), Utils.joinAsHtmlUnorderedList(emptyChangesetTags)),
557                    tr("Empty metadata"),
558                    JOptionPane.OK_CANCEL_OPTION,
559                    JOptionPane.WARNING_MESSAGE
560            )) {
561                dialog.handleMissingComment();
562                return;
563            }
564
565            UploadStrategySpecification strategy = dialog.getUploadStrategySpecification();
566            if (strategy.getStrategy().equals(UploadStrategy.CHUNKED_DATASET_STRATEGY)
567                    && strategy.getChunkSize() == UploadStrategySpecification.UNSPECIFIED_CHUNK_SIZE) {
568                warnIllegalChunkSize();
569                dialog.handleIllegalChunkSize();
570                return;
571            }
572            if (dialog instanceof AbstractUploadDialog) {
573                ((AbstractUploadDialog) dialog).setCanceled(false);
574                ((AbstractUploadDialog) dialog).setVisible(false);
575            }
576        }
577    }
578
579    /**
580     * Action for canceling the dialog.
581     */
582    static class CancelAction extends AbstractAction {
583
584        private final transient IUploadDialog dialog;
585
586        CancelAction(IUploadDialog dialog) {
587            this.dialog = dialog;
588            putValue(NAME, tr("Cancel"));
589            putValue(SMALL_ICON, ImageProvider.get("cancel"));
590            putValue(SHORT_DESCRIPTION, tr("Cancel the upload and resume editing"));
591        }
592
593        @Override
594        public void actionPerformed(ActionEvent e) {
595            if (dialog instanceof AbstractUploadDialog) {
596                ((AbstractUploadDialog) dialog).setCanceled(true);
597                ((AbstractUploadDialog) dialog).setVisible(false);
598            }
599        }
600    }
601
602    /**
603     * Listens to window closing events and processes them as cancel events.
604     * Listens to window open events and initializes user input
605     *
606     */
607    class WindowEventHandler extends WindowAdapter {
608        @Override
609        public void windowClosing(WindowEvent e) {
610            setCanceled(true);
611        }
612
613        @Override
614        public void windowActivated(WindowEvent arg0) {
615            if (tpConfigPanels.getSelectedIndex() == 0) {
616                pnlBasicUploadSettings.initEditingOfUploadComment();
617            }
618        }
619    }
620
621    /* -------------------------------------------------------------------------- */
622    /* Interface PropertyChangeListener                                           */
623    /* -------------------------------------------------------------------------- */
624    @Override
625    public void propertyChange(PropertyChangeEvent evt) {
626        if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) {
627            Changeset cs = (Changeset) evt.getNewValue();
628            setChangesetTags(dataSet);
629            if (cs == null) {
630                tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
631            } else {
632                tpConfigPanels.setTitleAt(1, tr("Tags of changeset {0}", cs.getId()));
633            }
634        }
635    }
636
637    /* -------------------------------------------------------------------------- */
638    /* Interface PreferenceChangedListener                                        */
639    /* -------------------------------------------------------------------------- */
640    @Override
641    public void preferenceChanged(PreferenceChangeEvent e) {
642        if (e.getKey() == null || !"osm-server.url".equals(e.getKey()))
643            return;
644        final Setting<?> newValue = e.getNewValue();
645        final String url;
646        if (newValue == null || newValue.getValue() == null) {
647            url = OsmApi.getOsmApi().getBaseUrl();
648        } else {
649            url = newValue.getValue().toString();
650        }
651        setTitle(tr("Upload to ''{0}''", url));
652    }
653
654    private static String getLastChangesetTagFromHistory(String historyKey, List<String> def) {
655        Collection<String> history = Main.pref.getCollection(historyKey, def);
656        int age = (int) (System.currentTimeMillis() / 1000 - Main.pref.getInteger(BasicUploadSettingsPanel.HISTORY_LAST_USED_KEY, 0));
657        if (history != null && age < Main.pref.getLong(BasicUploadSettingsPanel.HISTORY_MAX_AGE_KEY, TimeUnit.HOURS.toMillis(4))
658                && !history.isEmpty()) {
659            return history.iterator().next();
660        } else {
661            return null;
662        }
663    }
664
665    /**
666     * Returns the last changeset comment from history.
667     * @return the last changeset comment from history
668     */
669    public String getLastChangesetCommentFromHistory() {
670        return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.HISTORY_KEY, new ArrayList<String>());
671    }
672
673    /**
674     * Returns the last changeset source from history.
675     * @return the last changeset source from history
676     */
677    public String getLastChangesetSourceFromHistory() {
678        return getLastChangesetTagFromHistory(BasicUploadSettingsPanel.SOURCE_HISTORY_KEY, BasicUploadSettingsPanel.getDefaultSources());
679    }
680
681    @Override
682    public Map<String, String> getTags(boolean keepEmpty) {
683        return pnlTagSettings.getTags(keepEmpty);
684    }
685
686    @Override
687    public void handleMissingComment() {
688        tpConfigPanels.setSelectedIndex(0);
689        pnlBasicUploadSettings.initEditingOfUploadComment();
690    }
691
692    @Override
693    public void handleMissingSource() {
694        tpConfigPanels.setSelectedIndex(0);
695        pnlBasicUploadSettings.initEditingOfUploadSource();
696    }
697
698    @Override
699    public void handleIllegalChunkSize() {
700        tpConfigPanels.setSelectedIndex(0);
701    }
702
703    private static void bindCtrlEnterToAction(JComponent component, Action actionToBind) {
704        final KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK);
705        component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(stroke, "ctrl_enter");
706        component.getActionMap().put("ctrl_enter", actionToBind);
707    }
708}
Note: See TracBrowser for help on using the repository browser.