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.LEADING), 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.LEADING), 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.LEADING), 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}