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

Last change on this file since 12531 was 12531, checked in by simon04, 3 weeks ago

fix #15089 - UploadDialog: bind Ctrl+Enter to upload action

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