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.KeyEvent;
011import java.awt.event.WindowAdapter;
012import java.awt.event.WindowEvent;
013import java.util.ArrayList;
014import java.util.Collection;
015import java.util.List;
016
017import javax.swing.AbstractAction;
018import javax.swing.BorderFactory;
019import javax.swing.DefaultListModel;
020import javax.swing.JComponent;
021import javax.swing.JDialog;
022import javax.swing.JLabel;
023import javax.swing.JList;
024import javax.swing.JOptionPane;
025import javax.swing.JPanel;
026import javax.swing.JScrollPane;
027import javax.swing.KeyStroke;
028import javax.swing.event.ListSelectionEvent;
029import javax.swing.event.ListSelectionListener;
030
031import org.openstreetmap.josm.Main;
032import org.openstreetmap.josm.data.osm.Changeset;
033import org.openstreetmap.josm.gui.SideButton;
034import org.openstreetmap.josm.tools.ImageProvider;
035import org.openstreetmap.josm.tools.InputMapUtils;
036import org.openstreetmap.josm.tools.WindowGeometry;
037
038/**
039 * This dialog lets the user select changesets from a list of changesets.
040 * @since 2115
041 */
042public class CloseChangesetDialog extends JDialog {
043
044    /** the list */
045    private JList<Changeset> lstOpenChangesets;
046    /** true if the user canceled the dialog */
047    private boolean canceled;
048    /** the list model */
049    private DefaultListModel<Changeset> model;
050
051    private SideButton btnCloseChangesets;
052
053    protected JPanel buildTopPanel() {
054        JPanel pnl = new JPanel(new BorderLayout());
055        pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
056        pnl.add(new JLabel(tr("<html>Please select the changesets you want to close</html>")), BorderLayout.CENTER);
057        return pnl;
058    }
059
060    protected JPanel buildCenterPanel() {
061        JPanel pnl = new JPanel(new BorderLayout());
062        model = new DefaultListModel<>();
063        pnl.add(new JScrollPane(lstOpenChangesets = new JList<>(model)), BorderLayout.CENTER);
064        lstOpenChangesets.setCellRenderer(new ChangesetCellRenderer());
065        return pnl;
066    }
067
068    protected JPanel buildSouthPanel() {
069        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
070
071        // -- close action
072        CloseAction closeAction = new CloseAction();
073        lstOpenChangesets.addListSelectionListener(closeAction);
074        pnl.add(btnCloseChangesets = new SideButton(closeAction));
075        InputMapUtils.enableEnter(btnCloseChangesets);
076
077        // -- cancel action
078        SideButton btn;
079        pnl.add(btn = new SideButton(new CancelAction()));
080        btn.setFocusable(true);
081        return pnl;
082    }
083
084    protected void build() {
085        setTitle(tr("Open changesets"));
086        getContentPane().setLayout(new BorderLayout());
087        getContentPane().add(buildTopPanel(), BorderLayout.NORTH);
088        getContentPane().add(buildCenterPanel(), BorderLayout.CENTER);
089        getContentPane().add(buildSouthPanel(), BorderLayout.SOUTH);
090
091        getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "escape");
092        getRootPane().getActionMap().put("escape", new CancelAction());
093        addWindowListener(new WindowEventHandler());
094    }
095
096    @Override
097    public void setVisible(boolean visible) {
098        if (visible) {
099            new WindowGeometry(
100                    getClass().getName() + ".geometry",
101                    WindowGeometry.centerInWindow(Main.parent, new Dimension(300, 300))
102            ).applySafe(this);
103        } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
104            new WindowGeometry(this).remember(getClass().getName() + ".geometry");
105        }
106        super.setVisible(visible);
107    }
108
109    /**
110     * Constructs a new {@code CloseChangesetDialog}.
111     */
112    public CloseChangesetDialog() {
113        super(JOptionPane.getFrameForComponent(Main.parent), ModalityType.DOCUMENT_MODAL);
114        build();
115    }
116
117    class CloseAction extends AbstractAction implements ListSelectionListener {
118        CloseAction() {
119            putValue(NAME, tr("Close changesets"));
120            putValue(SMALL_ICON, ImageProvider.get("closechangeset"));
121            putValue(SHORT_DESCRIPTION, tr("Close the selected open changesets"));
122            refreshEnabledState();
123        }
124
125        @Override
126        public void actionPerformed(ActionEvent e) {
127            setCanceled(false);
128            setVisible(false);
129        }
130
131        protected void refreshEnabledState() {
132            List<Changeset> list = lstOpenChangesets.getSelectedValuesList();
133            setEnabled(list != null && !list.isEmpty());
134        }
135
136        @Override
137        public void valueChanged(ListSelectionEvent e) {
138            refreshEnabledState();
139        }
140    }
141
142    class CancelAction extends AbstractAction {
143
144        CancelAction() {
145            putValue(NAME, tr("Cancel"));
146            putValue(SMALL_ICON, ImageProvider.get("cancel"));
147            putValue(SHORT_DESCRIPTION, tr("Cancel closing of changesets"));
148        }
149
150        public void cancel() {
151            setCanceled(true);
152            setVisible(false);
153        }
154
155        @Override
156        public void actionPerformed(ActionEvent e) {
157            cancel();
158        }
159    }
160
161    class WindowEventHandler extends WindowAdapter {
162
163        @Override
164        public void windowActivated(WindowEvent arg0) {
165            btnCloseChangesets.requestFocusInWindow();
166        }
167
168        @Override
169        public void windowClosing(WindowEvent arg0) {
170            new CancelAction().cancel();
171        }
172
173    }
174
175    /**
176     * Replies true if this dialog was canceled
177     * @return true if this dialog was canceled
178     */
179    public boolean isCanceled() {
180        return canceled;
181    }
182
183    /**
184     * Sets whether this dialog is canceled
185     *
186     * @param canceled true, if this dialog is canceld
187     */
188    protected void setCanceled(boolean canceled) {
189        this.canceled = canceled;
190    }
191
192    /**
193     * Sets the collection of changesets to be displayed
194     *
195     * @param changesets the collection of changesets. Assumes an empty collection if null
196     */
197    public void setChangesets(Collection<Changeset> changesets) {
198        if (changesets == null) {
199            changesets = new ArrayList<>();
200        }
201        model.removeAllElements();
202        for (Changeset cs: changesets) {
203            model.addElement(cs);
204        }
205        if (!changesets.isEmpty()) {
206            lstOpenChangesets.getSelectionModel().setSelectionInterval(0, changesets.size()-1);
207        }
208    }
209
210    /**
211     * Replies a collection with the changesets the user selected.
212     * Never null, but may be empty.
213     *
214     * @return a collection with the changesets the user selected.
215     */
216    public Collection<Changeset> getSelectedChangesets() {
217        return lstOpenChangesets.getSelectedValuesList();
218    }
219}