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.Dimension;
008import java.awt.FlowLayout;
009import java.awt.event.ActionEvent;
010import java.awt.event.WindowAdapter;
011import java.awt.event.WindowEvent;
012import java.util.ArrayList;
013import java.util.Collection;
014import java.util.Collections;
015import java.util.Comparator;
016import java.util.List;
017
018import javax.swing.AbstractAction;
019import javax.swing.AbstractListModel;
020import javax.swing.Action;
021import javax.swing.BorderFactory;
022import javax.swing.JComponent;
023import javax.swing.JDialog;
024import javax.swing.JLabel;
025import javax.swing.JList;
026import javax.swing.JOptionPane;
027import javax.swing.JPanel;
028import javax.swing.JScrollPane;
029import javax.swing.JSplitPane;
030import javax.swing.KeyStroke;
031import javax.swing.ListSelectionModel;
032import javax.swing.event.ListSelectionEvent;
033import javax.swing.event.ListSelectionListener;
034
035import org.openstreetmap.josm.Main;
036import org.openstreetmap.josm.data.osm.OsmPrimitive;
037import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
038import org.openstreetmap.josm.gui.DefaultNameFormatter;
039import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
040import org.openstreetmap.josm.gui.SideButton;
041import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
042import org.openstreetmap.josm.gui.help.HelpUtil;
043import org.openstreetmap.josm.tools.ImageProvider;
044import org.openstreetmap.josm.tools.WindowGeometry;
045
046/**
047 * This dialog can be used to select individual object for uploading.
048 *
049 * @since 2250
050 */
051public class UploadSelectionDialog extends JDialog {
052
053    private OsmPrimitiveList lstSelectedPrimitives;
054    private OsmPrimitiveList lstDeletedPrimitives;
055    private JSplitPane spLists;
056    private boolean canceled;
057    private SideButton btnContinue;
058
059    /**
060     * Constructs a new {@code UploadSelectionDialog}.
061     */
062    public UploadSelectionDialog() {
063        super(JOptionPane.getFrameForComponent(Main.parent), ModalityType.DOCUMENT_MODAL);
064        build();
065    }
066
067    protected JPanel buildSelectedPrimitivesPanel() {
068        JPanel pnl = new JPanel();
069        pnl.setLayout(new BorderLayout());
070        JLabel lbl = new JLabel(
071                tr("<html>Mark modified objects <strong>from the current selection</strong> to be uploaded to the server.</html>"));
072        lbl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
073        pnl.add(lbl, BorderLayout.NORTH);
074        pnl.add(new JScrollPane(lstSelectedPrimitives = new OsmPrimitiveList()), BorderLayout.CENTER);
075        lbl.setLabelFor(lstSelectedPrimitives);
076        return pnl;
077    }
078
079    protected JPanel buildDeletedPrimitivesPanel() {
080        JPanel pnl = new JPanel();
081        pnl.setLayout(new BorderLayout());
082        JLabel lbl = new JLabel(tr("<html>Mark <strong>locally deleted objects</strong> to be deleted on the server.</html>"));
083        lbl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
084        pnl.add(lbl, BorderLayout.NORTH);
085        pnl.add(new JScrollPane(lstDeletedPrimitives = new OsmPrimitiveList()), BorderLayout.CENTER);
086        lbl.setLabelFor(lstDeletedPrimitives);
087        return pnl;
088    }
089
090    protected JPanel buildButtonPanel() {
091        JPanel pnl = new JPanel();
092        pnl.setLayout(new FlowLayout());
093        ContinueAction continueAction = new ContinueAction();
094        pnl.add(btnContinue = new SideButton(continueAction));
095        btnContinue.setFocusable(true);
096        lstDeletedPrimitives.getSelectionModel().addListSelectionListener(continueAction);
097        lstSelectedPrimitives.getSelectionModel().addListSelectionListener(continueAction);
098
099        pnl.add(new SideButton(new CancelAction()));
100        pnl.add(new SideButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/UploadSelection"))));
101        return pnl;
102    }
103
104    protected void build() {
105        setLayout(new BorderLayout());
106        spLists = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
107        spLists.setTopComponent(buildSelectedPrimitivesPanel());
108        spLists.setBottomComponent(buildDeletedPrimitivesPanel());
109        add(spLists, BorderLayout.CENTER);
110        add(buildButtonPanel(), BorderLayout.SOUTH);
111        addWindowListener(
112                new WindowAdapter() {
113                    @Override
114                    public void windowOpened(WindowEvent e) {
115                        spLists.setDividerLocation(0.5);
116                        btnContinue.requestFocusInWindow();
117                    }
118
119                    @Override
120                    public void windowClosing(WindowEvent e) {
121                        setCanceled(true);
122                    }
123                }
124        );
125        setTitle(tr("Select objects to upload"));
126        HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/UploadSelection"));
127    }
128
129    public void populate(Collection<OsmPrimitive> selected, Collection<OsmPrimitive> deleted) {
130        if (selected != null) {
131            lstSelectedPrimitives.getOsmPrimitiveListModel().setPrimitives(new ArrayList<>(selected));
132            if (!selected.isEmpty()) {
133                lstSelectedPrimitives.getSelectionModel().setSelectionInterval(0, selected.size()-1);
134            } else {
135                lstSelectedPrimitives.getSelectionModel().clearSelection();
136            }
137        } else {
138            lstSelectedPrimitives.getOsmPrimitiveListModel().setPrimitives(null);
139            lstSelectedPrimitives.getSelectionModel().clearSelection();
140        }
141
142        if (deleted != null) {
143            lstDeletedPrimitives.getOsmPrimitiveListModel().setPrimitives(new ArrayList<>(deleted));
144        } else {
145            lstDeletedPrimitives.getOsmPrimitiveListModel().setPrimitives(null);
146        }
147    }
148
149    public boolean isCanceled() {
150        return canceled;
151    }
152
153    protected void setCanceled(boolean canceled) {
154        this.canceled = canceled;
155    }
156
157    public List<OsmPrimitive> getSelectedPrimitives() {
158        List<OsmPrimitive> ret  = new ArrayList<>();
159        ret.addAll(lstSelectedPrimitives.getOsmPrimitiveListModel().getPrimitives(lstSelectedPrimitives.getSelectedIndices()));
160        ret.addAll(lstDeletedPrimitives.getOsmPrimitiveListModel().getPrimitives(lstDeletedPrimitives.getSelectedIndices()));
161        return ret;
162    }
163
164    @Override
165    public void setVisible(boolean visible) {
166        if (visible) {
167            new WindowGeometry(
168                    getClass().getName() + ".geometry",
169                    WindowGeometry.centerInWindow(
170                            Main.parent,
171                            new Dimension(200, 400)
172                    )
173            ).applySafe(this);
174        } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
175            new WindowGeometry(this).remember(getClass().getName() + ".geometry");
176        }
177        super.setVisible(visible);
178    }
179
180    static class OsmPrimitiveList extends JList<OsmPrimitive> {
181        protected void init() {
182            setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
183            setCellRenderer(new OsmPrimitivRenderer());
184        }
185
186        OsmPrimitiveList() {
187            this(new OsmPrimitiveListModel());
188        }
189
190        OsmPrimitiveList(OsmPrimitiveListModel model) {
191            super(model);
192            init();
193        }
194
195        public OsmPrimitiveListModel getOsmPrimitiveListModel() {
196            return (OsmPrimitiveListModel) getModel();
197        }
198    }
199
200    static class OsmPrimitiveListModel extends AbstractListModel<OsmPrimitive> {
201        private transient List<OsmPrimitive> data;
202
203        protected void sort() {
204            if (data == null)
205                return;
206            Collections.sort(
207                    data,
208                    new Comparator<OsmPrimitive>() {
209                        private DefaultNameFormatter formatter = DefaultNameFormatter.getInstance();
210                        @Override
211                        public int compare(OsmPrimitive o1, OsmPrimitive o2) {
212                            int ret = OsmPrimitiveType.from(o1).compareTo(OsmPrimitiveType.from(o2));
213                            if (ret != 0) return ret;
214                            return o1.getDisplayName(formatter).compareTo(o1.getDisplayName(formatter));
215                        }
216                    }
217            );
218        }
219
220        public void setPrimitives(List<OsmPrimitive> data) {
221            this.data = data;
222            sort();
223            if (data != null) {
224                fireContentsChanged(this, 0, data.size());
225            } else {
226                fireContentsChanged(this, 0, 0);
227            }
228        }
229
230        @Override
231        public OsmPrimitive getElementAt(int index) {
232            if (data == null)
233                return null;
234            return data.get(index);
235        }
236
237        @Override
238        public int getSize() {
239            if (data == null)
240                return 0;
241            return data.size();
242        }
243
244        public List<OsmPrimitive> getPrimitives(int[] indices) {
245            if (indices == null || indices.length == 0)
246                return Collections.emptyList();
247            List<OsmPrimitive> ret = new ArrayList<>(indices.length);
248            for (int i: indices) {
249                if (i < 0) {
250                    continue;
251                }
252                ret.add(data.get(i));
253            }
254            return ret;
255        }
256    }
257
258    class CancelAction extends AbstractAction {
259        CancelAction() {
260            putValue(Action.SHORT_DESCRIPTION, tr("Cancel uploading"));
261            putValue(Action.NAME, tr("Cancel"));
262            putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
263            getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
264            .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
265            getRootPane().getActionMap().put("ESCAPE", this);
266            setEnabled(true);
267        }
268
269        @Override
270        public void actionPerformed(ActionEvent e) {
271            setCanceled(true);
272            setVisible(false);
273        }
274    }
275
276    class ContinueAction extends AbstractAction implements ListSelectionListener {
277        ContinueAction() {
278            putValue(Action.SHORT_DESCRIPTION, tr("Continue uploading"));
279            putValue(Action.NAME, tr("Continue"));
280            putValue(Action.SMALL_ICON, ImageProvider.get("", "upload"));
281            updateEnabledState();
282        }
283
284        @Override
285        public void actionPerformed(ActionEvent e) {
286            setCanceled(false);
287            setVisible(false);
288        }
289
290        protected void updateEnabledState() {
291            setEnabled(lstSelectedPrimitives.getSelectedIndex() >= 0
292                    || lstDeletedPrimitives.getSelectedIndex() >= 0);
293        }
294
295        @Override
296        public void valueChanged(ListSelectionEvent e) {
297            updateEnabledState();
298        }
299    }
300}