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

Revision 4982, 12.4 KB checked in by stoecker, 3 months ago (diff)

see #7226 - patch by akks (fixed a bit) - fix shortcut deprecations

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