Changeset 18395 in josm for trunk/src/org/openstreetmap


Ignore:
Timestamp:
2022-03-13T08:07:10+01:00 (3 years ago)
Author:
GerdP
Message:

fix #21825: Delete relations by default when all members are deleted
Patch from taylor.smock:

  • Add table where users can deselect/select relations to delete, when a delete command will remove all members of the relation
Location:
trunk/src/org/openstreetmap/josm
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/DeleteAction.java

    r16840 r18395  
    1010import java.awt.event.KeyEvent;
    1111import java.util.Collection;
     12import java.util.Collections;
    1213
    1314import javax.swing.JOptionPane;
     
    2425import org.openstreetmap.josm.gui.dialogs.DeleteFromRelationConfirmationDialog;
    2526import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
     27import org.openstreetmap.josm.tools.Pair;
    2628import org.openstreetmap.josm.tools.Shortcut;
    2729
     
    5052        @Override
    5153        public boolean confirmDeletionFromRelation(Collection<RelationToChildReference> references) {
     54            return this.confirmDeletionFromRelation(references, Collections.emptyList());
     55        }
     56
     57        @Override
     58        public boolean confirmDeletionFromRelation(Collection<RelationToChildReference> references,
     59                Collection<Pair<Relation, Boolean>> parentsToDelete) {
    5260            DeleteFromRelationConfirmationDialog dialog = DeleteFromRelationConfirmationDialog.getInstance();
    5361            dialog.getModel().populate(references);
     62            dialog.getDeletedRelationsModel().populate(parentsToDelete);
    5463            dialog.setVisible(true);
    5564            return !dialog.isCanceled();
  • trunk/src/org/openstreetmap/josm/command/DeleteCommand.java

    r18208 r18395  
    3636import org.openstreetmap.josm.tools.CheckParameterUtil;
    3737import org.openstreetmap.josm.tools.ImageProvider;
     38import org.openstreetmap.josm.tools.Pair;
    3839import org.openstreetmap.josm.tools.Utils;
    3940
     
    102103         */
    103104        boolean confirmDeletionFromRelation(Collection<RelationToChildReference> references);
     105
     106        /**
     107         * Confirm before removing a collection of primitives from their parent relations, with the probability of
     108         * deleting the parents as well.
     109         * @param references the list of relation-to-child references
     110         * @param parentsToDelete the list of parents to delete (the boolean part will be {@code true} if the user wants
     111         *                        to delete the relation).
     112         * @return {@code true} if the user confirms the deletion
     113         * @since 18395
     114         */
     115        default boolean confirmDeletionFromRelation(Collection<RelationToChildReference> references,
     116                Collection<Pair<Relation, Boolean>> parentsToDelete) {
     117            // This is a default method. Ensure that all the booleans are false.
     118            parentsToDelete.forEach(pair -> pair.b = false);
     119            return confirmDeletionFromRelation(references);
     120        }
    104121    }
    105122
     
    438455        }
    439456
     457        // remove the objects from their parent relations
     458        //
     459        final Set<Relation> relationsToBeChanged = primitivesToDelete.stream()
     460                .flatMap(p -> p.referrers(Relation.class))
     461                .collect(Collectors.toSet());
     462        final Set<Relation> additionalRelationsToDelete = new HashSet<>();
     463        for (Relation cur : relationsToBeChanged) {
     464            List<RelationMember> newMembers = cur.getMembers();
     465            cur.getMembersFor(primitivesToDelete).forEach(newMembers::remove);
     466            cmds.add(new ChangeMembersCommand(cur, newMembers));
     467            // If we don't have any members, we probably ought to delete the relation as well.
     468            if (newMembers.isEmpty()) {
     469                additionalRelationsToDelete.add(cur);
     470            }
     471        }
     472
     473
    440474        // get a confirmation that the objects to delete can be removed from their parent relations
    441475        //
     
    443477            Set<RelationToChildReference> references = RelationToChildReference.getRelationToChildReferences(primitivesToDelete);
    444478            references.removeIf(ref -> ref.getParent().isDeleted());
    445             if (!references.isEmpty() && !callback.confirmDeletionFromRelation(references)) {
     479            final Collection<Pair<Relation, Boolean>> pairedRelations = additionalRelationsToDelete.stream()
     480                    /*
     481                     * Default to true, so that users have to make a choice to not delete.
     482                     * Default implementation converts it to false, so this should be safe.
     483                     */
     484                    .map(relation -> new Pair<>(relation, true)).collect(Collectors.toSet());
     485            if (!references.isEmpty() && !callback.confirmDeletionFromRelation(references, pairedRelations)) {
    446486                return null;
    447487            }
    448         }
    449 
    450         // remove the objects from their parent relations
    451         //
    452         final Set<Relation> relationsToBeChanged = primitivesToDelete.stream()
    453                 .flatMap(p -> p.referrers(Relation.class))
    454                 .collect(Collectors.toSet());
    455         for (Relation cur : relationsToBeChanged) {
    456             List<RelationMember> newMembers = cur.getMembers();
    457             cur.getMembersFor(primitivesToDelete).forEach(newMembers::remove);
    458             cmds.add(new ChangeMembersCommand(cur, newMembers));
     488            pairedRelations.stream().filter(pair -> Boolean.TRUE.equals(pair.b)).map(pair -> pair.a)
     489                    .forEach(primitivesToDelete::add);
    459490        }
    460491
  • trunk/src/org/openstreetmap/josm/gui/dialogs/DeleteFromRelationConfirmationDialog.java

    r17440 r18395  
    99import java.awt.Dimension;
    1010import java.awt.FlowLayout;
     11import java.awt.GridBagLayout;
    1112import java.awt.event.ActionEvent;
    1213import java.awt.event.WindowAdapter;
     
    3536import org.openstreetmap.josm.data.osm.NameFormatter;
    3637import org.openstreetmap.josm.data.osm.OsmPrimitive;
     38import org.openstreetmap.josm.data.osm.Relation;
    3739import org.openstreetmap.josm.data.osm.RelationToChildReference;
    3840import org.openstreetmap.josm.gui.MainApplication;
     
    4345import org.openstreetmap.josm.gui.util.WindowGeometry;
    4446import org.openstreetmap.josm.gui.widgets.HtmlPanel;
     47import org.openstreetmap.josm.tools.GBC;
    4548import org.openstreetmap.josm.tools.I18n;
    4649import org.openstreetmap.josm.tools.ImageProvider;
     50import org.openstreetmap.josm.tools.Pair;
     51import org.openstreetmap.josm.tools.Utils;
    4752
    4853/**
     
    6974    /** the data model */
    7075    private RelationMemberTableModel model;
     76    /** The data model for deleting relations */
     77    private RelationDeleteModel deletedRelationsModel;
     78    /** The table to hide/show if the relations to delete are not empty*/
    7179    private final HtmlPanel htmlPanel = new HtmlPanel();
    7280    private boolean canceled;
     
    7583    protected JPanel buildRelationMemberTablePanel() {
    7684        JTable table = new JTable(model, new RelationMemberTableColumnModel());
    77         JPanel pnl = new JPanel(new BorderLayout());
    78         pnl.add(new JScrollPane(table));
     85        JPanel pnl = new JPanel(new GridBagLayout());
     86        pnl.add(new JScrollPane(table), GBC.eol().fill());
     87        JTable deletedRelationsTable = new JTable(this.deletedRelationsModel, new RelationDeleteTableColumnModel());
     88        JScrollPane deletedRelationsModelTableScrollPane = new JScrollPane(deletedRelationsTable);
     89        this.deletedRelationsModel.addTableModelListener(
     90                e -> deletedRelationsModelTableScrollPane.setVisible(this.deletedRelationsModel.getRowCount() > 0));
     91        // Default to not visible
     92        deletedRelationsModelTableScrollPane.setVisible(false);
     93        pnl.add(deletedRelationsModelTableScrollPane, GBC.eol().fill());
    7994        return pnl;
    8095    }
     
    92107        model = new RelationMemberTableModel();
    93108        model.addTableModelListener(this);
     109        this.deletedRelationsModel = new RelationDeleteModel();
     110        this.deletedRelationsModel.addTableModelListener(this);
    94111        getContentPane().setLayout(new BorderLayout());
    95112        getContentPane().add(htmlPanel, BorderLayout.NORTH);
     
    103120
    104121    protected void updateMessage() {
    105         int numObjectsToDelete = model.getNumObjectsToDelete();
    106         int numParentRelations = model.getNumParentRelations();
     122        int numObjectsToDelete = this.model.getNumObjectsToDelete() + this.deletedRelationsModel.getNumObjectsToDelete();
     123        int numParentRelations = this.model.getNumParentRelations() + this.deletedRelationsModel.getNumParentRelations();
    107124        final String msg1 = trn(
    108125                "Please confirm to remove <strong>{0} object</strong>.",
     
    120137
    121138    protected void updateTitle() {
    122         int numObjectsToDelete = model.getNumObjectsToDelete();
     139        int numObjectsToDelete = this.model.getNumObjectsToDelete() + this.deletedRelationsModel.getNumObjectsToDelete();
    123140        if (numObjectsToDelete > 0) {
    124141            setTitle(trn("Deleting {0} object", "Deleting {0} objects", numObjectsToDelete, numObjectsToDelete));
     
    143160    public RelationMemberTableModel getModel() {
    144161        return model;
     162    }
     163
     164    /**
     165     * Replies the data model used for relations that should probably be deleted.
     166     * @return the data model
     167     * @since 18395
     168     */
     169    public RelationDeleteModel getDeletedRelationsModel() {
     170        return this.deletedRelationsModel;
    145171    }
    146172
     
    174200            }
    175201            model.data.clear();
     202            this.deletedRelationsModel.data.clear();
    176203        }
    177204        super.setVisible(visible);
     
    326353    }
    327354
     355    /**
     356     * The table model which manages relations that will be deleted, if their children are deleted.
     357     * @since 18395
     358     */
     359    public static class RelationDeleteModel extends DefaultTableModel {
     360        private final transient List<Pair<Relation, Boolean>> data = new ArrayList<>();
     361
     362        @Override
     363        public int getRowCount() {
     364            // This is called in the super constructor. Before we have instantiated the list. Removing the null check
     365            // WILL LEAD TO A SILENT NPE!
     366            if (this.data == null) {
     367                return 0;
     368            }
     369            return this.data.size();
     370        }
     371
     372        /**
     373         * Sets the data that should be displayed in the list.
     374         * @param references A list of references to display
     375         */
     376        public void populate(Collection<Pair<Relation, Boolean>> references) {
     377            this.data.clear();
     378            if (references != null) {
     379                this.data.addAll(references);
     380            }
     381            this.data.sort(Comparator.comparing(pair -> pair.a));
     382            fireTableDataChanged();
     383        }
     384
     385        /**
     386         * Gets the list of children that are currently displayed.
     387         * @return The children.
     388         */
     389        public Set<Relation> getObjectsToDelete() {
     390            return this.data.stream().filter(relation -> relation.b).map(relation -> relation.a).collect(Collectors.toSet());
     391        }
     392
     393        /**
     394         * Gets the number of elements {@link #getObjectsToDelete()} would return.
     395         * @return That number.
     396         */
     397        public int getNumObjectsToDelete() {
     398            return getObjectsToDelete().size();
     399        }
     400
     401        /**
     402         * Gets the set of parent relations
     403         * @return All parent relations of the references
     404         */
     405        public Set<OsmPrimitive> getParentRelations() {
     406            return this.data.stream()
     407                    .flatMap(pair -> Utils.filteredCollection(pair.a.getReferrers(), Relation.class).stream())
     408                    .collect(Collectors.toSet());
     409        }
     410
     411        /**
     412         * Gets the number of elements {@link #getParentRelations()} would return.
     413         * @return That number.
     414         */
     415        public int getNumParentRelations() {
     416            return getParentRelations().size();
     417        }
     418
     419        @Override
     420        public Object getValueAt(int rowIndex, int columnIndex) {
     421            if (this.data.isEmpty()) {
     422                return null;
     423            }
     424            Pair<Relation, Boolean> ref = this.data.get(rowIndex);
     425            switch(columnIndex) {
     426            case 0: return ref.a;
     427            case 1: return ref.b;
     428            default:
     429                assert false : "Illegal column index";
     430            }
     431            return null;
     432        }
     433
     434        @Override
     435        public boolean isCellEditable(int row, int column) {
     436            return !this.data.isEmpty() && column == 1;
     437        }
     438
     439        @Override
     440        public void setValueAt(Object aValue, int row, int column) {
     441            if (this.data.size() > row && column == 1 && aValue instanceof Boolean) {
     442                this.data.get(row).b = ((Boolean) aValue);
     443            }
     444        }
     445
     446        @Override
     447        public Class<?> getColumnClass(int columnIndex) {
     448            switch (columnIndex) {
     449            case 0:
     450                return Relation.class;
     451            case 1:
     452                return Boolean.class;
     453            default:
     454                return super.getColumnClass(columnIndex);
     455            }
     456        }
     457    }
     458
     459    private static class RelationDeleteTableColumnModel extends DefaultTableColumnModel {
     460        protected final void createColumns() {
     461            // column 0 - To Delete
     462            TableColumn col = new TableColumn(0);
     463            col.setHeaderValue(tr("Relation"));
     464            col.setResizable(true);
     465            col.setWidth(100);
     466            col.setPreferredWidth(100);
     467            col.setCellRenderer(new PrimitiveRenderer());
     468            addColumn(col);
     469
     470            // column 0 - From Relation
     471            col = new TableColumn(1);
     472            final String toDelete = tr("To delete");
     473            col.setHeaderValue(toDelete);
     474            col.setResizable(true);
     475            col.setPreferredWidth(toDelete.length());
     476            addColumn(col);
     477        }
     478
     479        RelationDeleteTableColumnModel() {
     480            createColumns();
     481        }
     482    }
     483
    328484    class OKAction extends AbstractAction {
    329485        OKAction() {
Note: See TracChangeset for help on using the changeset viewer.