source: josm/trunk/src/org/openstreetmap/josm/actions/PurgeAction.java@ 9245

Last change on this file since 9245 was 9233, checked in by simon04, 8 years ago

fix #4602 - Add "Do not show again" to purge action

  • Property svn:eol-style set to native
File size: 13.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.Dimension;
8import java.awt.GraphicsEnvironment;
9import java.awt.GridBagLayout;
10import java.awt.Insets;
11import java.awt.event.ActionEvent;
12import java.awt.event.KeyEvent;
13import java.util.ArrayList;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.Comparator;
17import java.util.HashSet;
18import java.util.List;
19import java.util.Set;
20
21import javax.swing.AbstractAction;
22import javax.swing.BorderFactory;
23import javax.swing.Box;
24import javax.swing.JButton;
25import javax.swing.JCheckBox;
26import javax.swing.JLabel;
27import javax.swing.JList;
28import javax.swing.JOptionPane;
29import javax.swing.JPanel;
30import javax.swing.JScrollPane;
31import javax.swing.JSeparator;
32
33import org.openstreetmap.josm.Main;
34import org.openstreetmap.josm.command.PurgeCommand;
35import org.openstreetmap.josm.data.osm.Node;
36import org.openstreetmap.josm.data.osm.OsmPrimitive;
37import org.openstreetmap.josm.data.osm.Relation;
38import org.openstreetmap.josm.data.osm.RelationMember;
39import org.openstreetmap.josm.data.osm.Way;
40import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
41import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
42import org.openstreetmap.josm.gui.help.HelpUtil;
43import org.openstreetmap.josm.gui.layer.OsmDataLayer;
44import org.openstreetmap.josm.tools.GBC;
45import org.openstreetmap.josm.tools.ImageProvider;
46import org.openstreetmap.josm.tools.Shortcut;
47
48/**
49 * The action to purge the selected primitives, i.e. remove them from the
50 * data layer, or remove their content and make them incomplete.
51 *
52 * This means, the deleted flag is not affected and JOSM simply forgets
53 * about these primitives.
54 *
55 * This action is undo-able. In order not to break previous commands in the
56 * undo buffer, we must re-add the identical object (and not semantically
57 * equal ones).
58 */
59public class PurgeAction extends JosmAction {
60
61 /**
62 * Constructs a new {@code PurgeAction}.
63 */
64 public PurgeAction() {
65 /* translator note: other expressions for "purge" might be "forget", "clean", "obliterate", "prune" */
66 super(tr("Purge..."), "purge", tr("Forget objects but do not delete them on server when uploading."),
67 Shortcut.registerShortcut("system:purge", tr("Edit: {0}", tr("Purge")),
68 KeyEvent.VK_P, Shortcut.CTRL_SHIFT),
69 true);
70 putValue("help", HelpUtil.ht("/Action/Purge"));
71 }
72
73 protected transient OsmDataLayer layer;
74 protected JCheckBox cbClearUndoRedo;
75
76 protected transient Set<OsmPrimitive> toPurge;
77 /**
78 * finally, contains all objects that are purged
79 */
80 protected transient Set<OsmPrimitive> toPurgeChecked;
81 /**
82 * Subset of toPurgeChecked. Marks primitives that remain in the
83 * dataset, but incomplete.
84 */
85 protected transient Set<OsmPrimitive> makeIncomplete;
86 /**
87 * Subset of toPurgeChecked. Those that have not been in the selection.
88 */
89 protected transient List<OsmPrimitive> toPurgeAdditionally;
90
91 @Override
92 public void actionPerformed(ActionEvent e) {
93 if (!isEnabled())
94 return;
95
96 Collection<OsmPrimitive> sel = getCurrentDataSet().getAllSelected();
97 layer = Main.main.getEditLayer();
98
99 toPurge = new HashSet<>(sel);
100 toPurgeAdditionally = new ArrayList<>();
101 toPurgeChecked = new HashSet<>();
102
103 // Add referrer, unless the object to purge is not new
104 // and the parent is a relation
105 Set<OsmPrimitive> toPurgeRecursive = new HashSet<>();
106 while (!toPurge.isEmpty()) {
107
108 for (OsmPrimitive osm: toPurge) {
109 for (OsmPrimitive parent: osm.getReferrers()) {
110 if (toPurge.contains(parent) || toPurgeChecked.contains(parent) || toPurgeRecursive.contains(parent)) {
111 continue;
112 }
113 if (parent instanceof Way || (parent instanceof Relation && osm.isNew())) {
114 toPurgeAdditionally.add(parent);
115 toPurgeRecursive.add(parent);
116 }
117 }
118 toPurgeChecked.add(osm);
119 }
120 toPurge = toPurgeRecursive;
121 toPurgeRecursive = new HashSet<>();
122 }
123
124 makeIncomplete = new HashSet<>();
125
126 // Find the objects that will be incomplete after purging.
127 // At this point, all parents of new to-be-purged primitives are
128 // also to-be-purged and
129 // all parents of not-new to-be-purged primitives are either
130 // to-be-purged or of type relation.
131 TOP:
132 for (OsmPrimitive child : toPurgeChecked) {
133 if (child.isNew()) {
134 continue;
135 }
136 for (OsmPrimitive parent : child.getReferrers()) {
137 if (parent instanceof Relation && !toPurgeChecked.contains(parent)) {
138 makeIncomplete.add(child);
139 continue TOP;
140 }
141 }
142 }
143
144 // Add untagged way nodes. Do not add nodes that have other
145 // referrers not yet to-be-purged.
146 if (Main.pref.getBoolean("purge.add_untagged_waynodes", true)) {
147 Set<OsmPrimitive> wayNodes = new HashSet<>();
148 for (OsmPrimitive osm : toPurgeChecked) {
149 if (osm instanceof Way) {
150 Way w = (Way) osm;
151 NODE:
152 for (Node n : w.getNodes()) {
153 if (n.isTagged() || toPurgeChecked.contains(n)) {
154 continue;
155 }
156 for (OsmPrimitive ref : n.getReferrers()) {
157 if (ref != w && !toPurgeChecked.contains(ref)) {
158 continue NODE;
159 }
160 }
161 wayNodes.add(n);
162 }
163 }
164 }
165 toPurgeChecked.addAll(wayNodes);
166 toPurgeAdditionally.addAll(wayNodes);
167 }
168
169 if (Main.pref.getBoolean("purge.add_relations_with_only_incomplete_members", true)) {
170 Set<Relation> relSet = new HashSet<>();
171 for (OsmPrimitive osm : toPurgeChecked) {
172 for (OsmPrimitive parent : osm.getReferrers()) {
173 if (parent instanceof Relation
174 && !(toPurgeChecked.contains(parent))
175 && hasOnlyIncompleteMembers((Relation) parent, toPurgeChecked, relSet)) {
176 relSet.add((Relation) parent);
177 }
178 }
179 }
180
181 /**
182 * Add higher level relations (list gets extended while looping over it)
183 */
184 List<Relation> relLst = new ArrayList<>(relSet);
185 for (int i = 0; i < relLst.size(); ++i) { // foreach loop not applicable since list gets extended while looping over it
186 for (OsmPrimitive parent : relLst.get(i).getReferrers()) {
187 if (!(toPurgeChecked.contains(parent))
188 && hasOnlyIncompleteMembers((Relation) parent, toPurgeChecked, relLst)) {
189 relLst.add((Relation) parent);
190 }
191 }
192 }
193 relSet = new HashSet<>(relLst);
194 toPurgeChecked.addAll(relSet);
195 toPurgeAdditionally.addAll(relSet);
196 }
197
198 boolean modified = false;
199 for (OsmPrimitive osm : toPurgeChecked) {
200 if (osm.isModified()) {
201 modified = true;
202 break;
203 }
204 }
205
206 boolean clearUndoRedo = false;
207
208 if (!GraphicsEnvironment.isHeadless()) {
209 final boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
210 "purge", Main.parent, buildPanel(modified), tr("Confirm Purging"),
211 JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_OPTION);
212 if (!answer)
213 return;
214
215 clearUndoRedo = cbClearUndoRedo.isSelected();
216 Main.pref.put("purge.clear_undo_redo", clearUndoRedo);
217 }
218
219 Main.main.undoRedo.add(new PurgeCommand(Main.main.getEditLayer(), toPurgeChecked, makeIncomplete));
220
221 if (clearUndoRedo) {
222 Main.main.undoRedo.clean();
223 getCurrentDataSet().clearSelectionHistory();
224 }
225 }
226
227 private JPanel buildPanel(boolean modified) {
228 JPanel pnl = new JPanel(new GridBagLayout());
229
230 pnl.add(Box.createRigidArea(new Dimension(400, 0)), GBC.eol().fill(GBC.HORIZONTAL));
231
232 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
233 pnl.add(new JLabel("<html>"+
234 tr("This operation makes JOSM forget the selected objects.<br> " +
235 "They will be removed from the layer, but <i>not</i> deleted<br> " +
236 "on the server when uploading.")+"</html>",
237 ImageProvider.get("purge"), JLabel.LEFT), GBC.eol().fill(GBC.HORIZONTAL));
238
239 if (!toPurgeAdditionally.isEmpty()) {
240 pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
241 pnl.add(new JLabel("<html>"+
242 tr("The following dependent objects will be purged<br> " +
243 "in addition to the selected objects:")+"</html>",
244 ImageProvider.get("warning-small"), JLabel.LEFT), GBC.eol().fill(GBC.HORIZONTAL));
245
246 Collections.sort(toPurgeAdditionally, new Comparator<OsmPrimitive>() {
247 @Override
248 public int compare(OsmPrimitive o1, OsmPrimitive o2) {
249 int type = o2.getType().compareTo(o1.getType());
250 if (type != 0)
251 return type;
252 return Long.compare(o1.getUniqueId(), o2.getUniqueId());
253 }
254 });
255 JList<OsmPrimitive> list = new JList<>(toPurgeAdditionally.toArray(new OsmPrimitive[toPurgeAdditionally.size()]));
256 /* force selection to be active for all entries */
257 list.setCellRenderer(new OsmPrimitivRenderer() {
258 @Override
259 public Component getListCellRendererComponent(JList<? extends OsmPrimitive> list,
260 OsmPrimitive value,
261 int index,
262 boolean isSelected,
263 boolean cellHasFocus) {
264 return super.getListCellRendererComponent(list, value, index, true, false);
265 }
266 });
267 JScrollPane scroll = new JScrollPane(list);
268 scroll.setPreferredSize(new Dimension(250, 300));
269 scroll.setMinimumSize(new Dimension(250, 300));
270 pnl.add(scroll, GBC.std().fill(GBC.BOTH).weight(1.0, 1.0));
271
272 JButton addToSelection = new JButton(new AbstractAction() {
273 {
274 putValue(SHORT_DESCRIPTION, tr("Add to selection"));
275 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
276 }
277
278 @Override
279 public void actionPerformed(ActionEvent e) {
280 layer.data.addSelected(toPurgeAdditionally);
281 }
282 });
283 addToSelection.setMargin(new Insets(0, 0, 0, 0));
284 pnl.add(addToSelection, GBC.eol().anchor(GBC.SOUTHWEST).weight(0.0, 1.0).insets(2, 0, 0, 3));
285 }
286
287 if (modified) {
288 pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
289 pnl.add(new JLabel("<html>"+tr("Some of the objects are modified.<br> " +
290 "Proceed, if these changes should be discarded."+"</html>"),
291 ImageProvider.get("warning-small"), JLabel.LEFT),
292 GBC.eol().fill(GBC.HORIZONTAL));
293 }
294
295 cbClearUndoRedo = new JCheckBox(tr("Clear Undo/Redo buffer"));
296 cbClearUndoRedo.setSelected(Main.pref.getBoolean("purge.clear_undo_redo", false));
297
298 pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5));
299 pnl.add(cbClearUndoRedo, GBC.eol());
300 return pnl;
301 }
302
303 @Override
304 protected void updateEnabledState() {
305 if (getCurrentDataSet() == null) {
306 setEnabled(false);
307 } else {
308 setEnabled(!(getCurrentDataSet().selectionEmpty()));
309 }
310 }
311
312 @Override
313 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
314 setEnabled(selection != null && !selection.isEmpty());
315 }
316
317 private static boolean hasOnlyIncompleteMembers(
318 Relation r, Collection<OsmPrimitive> toPurge, Collection<? extends OsmPrimitive> moreToPurge) {
319 for (RelationMember m : r.getMembers()) {
320 if (!m.getMember().isIncomplete() && !toPurge.contains(m.getMember()) && !moreToPurge.contains(m.getMember()))
321 return false;
322 }
323 return true;
324 }
325}
Note: See TracBrowser for help on using the repository browser.