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

Last change on this file since 9171 was 8984, checked in by Don-vip, 8 years ago

see #12038 - add non regression test

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