001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.Dimension;
008import java.awt.GraphicsEnvironment;
009import java.awt.GridBagLayout;
010import java.awt.Insets;
011import java.awt.event.ActionEvent;
012import java.awt.event.KeyEvent;
013import java.util.ArrayList;
014import java.util.Collection;
015import java.util.List;
016
017import javax.swing.AbstractAction;
018import javax.swing.BorderFactory;
019import javax.swing.Box;
020import javax.swing.JButton;
021import javax.swing.JCheckBox;
022import javax.swing.JLabel;
023import javax.swing.JList;
024import javax.swing.JOptionPane;
025import javax.swing.JPanel;
026import javax.swing.JScrollPane;
027import javax.swing.JSeparator;
028
029import org.openstreetmap.josm.command.PurgeCommand;
030import org.openstreetmap.josm.data.UndoRedoHandler;
031import org.openstreetmap.josm.data.osm.DataSet;
032import org.openstreetmap.josm.data.osm.IPrimitive;
033import org.openstreetmap.josm.data.osm.OsmPrimitive;
034import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
035import org.openstreetmap.josm.gui.MainApplication;
036import org.openstreetmap.josm.gui.PrimitiveRenderer;
037import org.openstreetmap.josm.gui.help.HelpUtil;
038import org.openstreetmap.josm.gui.layer.OsmDataLayer;
039import org.openstreetmap.josm.spi.preferences.Config;
040import org.openstreetmap.josm.tools.GBC;
041import org.openstreetmap.josm.tools.ImageProvider;
042import org.openstreetmap.josm.tools.Shortcut;
043
044/**
045 * The action to purge the selected primitives, i.e. remove them from the
046 * data layer, or remove their content and make them incomplete.
047 *
048 * This means, the deleted flag is not affected and JOSM simply forgets
049 * about these primitives.
050 *
051 * This action is undo-able. In order not to break previous commands in the
052 * undo buffer, we must re-add the identical object (and not semantically equal ones).
053 *
054 * @since 3431
055 */
056public class PurgeAction extends JosmAction {
057
058    protected transient OsmDataLayer layer;
059    protected JCheckBox cbClearUndoRedo;
060    protected boolean modified;
061
062    /**
063     * Subset of toPurgeChecked. Those that have not been in the selection.
064     */
065    protected transient List<OsmPrimitive> toPurgeAdditionally;
066
067    /**
068     * Constructs a new {@code PurgeAction}.
069     */
070    public PurgeAction() {
071        /* translator note: other expressions for "purge" might be "forget", "clean", "obliterate", "prune" */
072        super(tr("Purge..."), "purge", tr("Forget objects but do not delete them on server when uploading."),
073                Shortcut.registerShortcut("system:purge", tr("Edit: {0}", tr("Purge")), KeyEvent.VK_P, Shortcut.CTRL_SHIFT),
074                true);
075        setHelpId(HelpUtil.ht("/Action/Purge"));
076    }
077
078    /** force selection to be active for all entries */
079    static class SelectionForcedPrimitiveRenderer extends PrimitiveRenderer {
080        @Override
081        public Component getListCellRendererComponent(JList<? extends IPrimitive> list,
082                IPrimitive value, int index, boolean isSelected, boolean cellHasFocus) {
083            return super.getListCellRendererComponent(list, value, index, true, false);
084        }
085    }
086
087    @Override
088    public void actionPerformed(ActionEvent e) {
089        if (!isEnabled())
090            return;
091
092        PurgeCommand cmd = getPurgeCommand(getLayerManager().getEditDataSet().getAllSelected());
093        boolean clearUndoRedo = false;
094
095        if (!GraphicsEnvironment.isHeadless()) {
096            final boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
097                    "purge", MainApplication.getMainFrame(), buildPanel(modified), tr("Confirm Purging"),
098                    JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_OPTION);
099            if (!answer)
100                return;
101
102            clearUndoRedo = cbClearUndoRedo.isSelected();
103            Config.getPref().putBoolean("purge.clear_undo_redo", clearUndoRedo);
104        }
105
106        UndoRedoHandler.getInstance().add(cmd);
107        if (clearUndoRedo) {
108            UndoRedoHandler.getInstance().clean();
109            getLayerManager().getEditDataSet().clearSelectionHistory();
110        }
111    }
112
113    /**
114     * Creates command to purge selected OSM primitives.
115     * @param sel selected OSM primitives
116     * @return command to purge selected OSM primitives
117     * @since 11252
118     */
119    public PurgeCommand getPurgeCommand(Collection<OsmPrimitive> sel) {
120        layer = getLayerManager().getEditLayer();
121        toPurgeAdditionally = new ArrayList<>();
122        PurgeCommand cmd = PurgeCommand.build(sel, toPurgeAdditionally);
123        modified = cmd.getParticipatingPrimitives().stream().anyMatch(OsmPrimitive::isModified);
124        return cmd;
125    }
126
127    private JPanel buildPanel(boolean modified) {
128        JPanel pnl = new JPanel(new GridBagLayout());
129
130        pnl.add(Box.createRigidArea(new Dimension(400, 0)), GBC.eol().fill(GBC.HORIZONTAL));
131
132        pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
133        pnl.add(new JLabel("<html>"+
134                tr("This operation makes JOSM forget the selected objects.<br> " +
135                        "They will be removed from the layer, but <i>not</i> deleted<br> " +
136                        "on the server when uploading.")+"</html>",
137                        ImageProvider.get("purge"), JLabel.LEFT), GBC.eol().fill(GBC.HORIZONTAL));
138
139        if (!toPurgeAdditionally.isEmpty()) {
140            pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
141            pnl.add(new JLabel("<html>"+
142                    tr("The following dependent objects will be purged<br> " +
143                            "in addition to the selected objects:")+"</html>",
144                            ImageProvider.get("warning-small"), JLabel.LEFT), GBC.eol().fill(GBC.HORIZONTAL));
145
146            toPurgeAdditionally.sort((o1, o2) -> {
147                int type = o2.getType().compareTo(o1.getType());
148                if (type != 0)
149                    return type;
150                return Long.compare(o1.getUniqueId(), o2.getUniqueId());
151            });
152            JList<OsmPrimitive> list = new JList<>(toPurgeAdditionally.toArray(new OsmPrimitive[0]));
153            /* force selection to be active for all entries */
154            list.setCellRenderer(new SelectionForcedPrimitiveRenderer());
155            JScrollPane scroll = new JScrollPane(list);
156            scroll.setPreferredSize(new Dimension(250, 300));
157            scroll.setMinimumSize(new Dimension(250, 300));
158            pnl.add(scroll, GBC.std().fill(GBC.BOTH).weight(1.0, 1.0));
159
160            JButton addToSelection = new JButton(new AbstractAction() {
161                {
162                    putValue(SHORT_DESCRIPTION, tr("Add to selection"));
163                    new ImageProvider("dialogs", "select").getResource().attachImageIcon(this, true);
164                }
165
166                @Override
167                public void actionPerformed(ActionEvent e) {
168                    layer.data.addSelected(toPurgeAdditionally);
169                }
170            });
171            addToSelection.setMargin(new Insets(0, 0, 0, 0));
172            pnl.add(addToSelection, GBC.eol().anchor(GBC.SOUTHWEST).weight(0.0, 1.0).insets(2, 0, 0, 3));
173        }
174
175        if (modified) {
176            pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
177            pnl.add(new JLabel("<html>"+tr("Some of the objects are modified.<br> " +
178                    "Proceed, if these changes should be discarded."+"</html>"),
179                    ImageProvider.get("warning-small"), JLabel.LEFT),
180                    GBC.eol().fill(GBC.HORIZONTAL));
181        }
182
183        cbClearUndoRedo = new JCheckBox(tr("Clear Undo/Redo buffer"));
184        cbClearUndoRedo.setSelected(Config.getPref().getBoolean("purge.clear_undo_redo", false));
185
186        pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
187        pnl.add(cbClearUndoRedo, GBC.eol());
188        return pnl;
189    }
190
191    @Override
192    protected void updateEnabledState() {
193        DataSet ds = getLayerManager().getEditDataSet();
194        setEnabled(ds != null && !ds.selectionEmpty());
195    }
196
197    @Override
198    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
199        updateEnabledStateOnModifiableSelection(selection);
200    }
201}