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

Last change on this file since 11385 was 11343, checked in by Don-vip, 7 years ago

findbugs - disable SE_TRANSIENT_FIELD_NOT_RESTORED, fix some SIC_INNER_SHOULD_BE_STATIC_ANON

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