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