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

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

see #15182 - deprecate Main.main.undoRedo. Replacement: gui.MainApplication.undoRedo

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