Ticket #21825: 21825.patch

File 21825.patch, 16.1 KB (added by taylor.smock, 4 years ago)

Add table where users can deselect/select relations to delete, when a delete command will remove all members of the relation

  • src/org/openstreetmap/josm/actions/DeleteAction.java

    diff --git a/src/org/openstreetmap/josm/actions/DeleteAction.java b/src/org/openstreetmap/josm/actions/DeleteAction.java
    index 4400aea798..a480c2efae 100644
    a b import java.awt.GridBagLayout;  
    99import java.awt.event.ActionEvent;
    1010import java.awt.event.KeyEvent;
    1111import java.util.Collection;
     12import java.util.Collections;
    1213
    1314import javax.swing.JOptionPane;
    1415import javax.swing.JPanel;
    import org.openstreetmap.josm.gui.MainApplication;  
    2324import org.openstreetmap.josm.gui.MapFrame;
    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
    2830/**
    public final class DeleteAction extends JosmAction {  
    4951
    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();
    5665        }
  • src/org/openstreetmap/josm/command/DeleteCommand.java

    diff --git a/src/org/openstreetmap/josm/command/DeleteCommand.java b/src/org/openstreetmap/josm/command/DeleteCommand.java
    index e21234fea6..82dab79d38 100644
    a b import org.openstreetmap.josm.data.osm.Way;  
    3535import org.openstreetmap.josm.data.osm.WaySegment;
    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
    4041/**
    public class DeleteCommand extends Command {  
    101102         * @since 12763
    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 xxx
     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
    106123    private static volatile DeletionCallback callback;
    public class DeleteCommand extends Command {  
    437454            }
    438455        }
    439456
    440         // get a confirmation that the objects to delete can be removed from their parent relations
    441         //
    442         if (!silent) {
    443             Set<RelationToChildReference> references = RelationToChildReference.getRelationToChildReferences(primitivesToDelete);
    444             references.removeIf(ref -> ref.getParent().isDeleted());
    445             if (!references.isEmpty() && !callback.confirmDeletionFromRelation(references)) {
    446                 return null;
    447             }
    448         }
    449 
    450457        // remove the objects from their parent relations
    451458        //
    452459        final Set<Relation> relationsToBeChanged = primitivesToDelete.stream()
    453460                .flatMap(p -> p.referrers(Relation.class))
    454461                .collect(Collectors.toSet());
     462        final Set<Relation> additionalRelationsToDelete = new HashSet<>();
    455463        for (Relation cur : relationsToBeChanged) {
    456464            List<RelationMember> newMembers = cur.getMembers();
    457465            cur.getMembersFor(primitivesToDelete).forEach(newMembers::remove);
    458466            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
     474        // get a confirmation that the objects to delete can be removed from their parent relations
     475        //
     476        if (!silent) {
     477            Set<RelationToChildReference> references = RelationToChildReference.getRelationToChildReferences(primitivesToDelete);
     478            references.removeIf(ref -> ref.getParent().isDeleted());
     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)) {
     486                return null;
     487            }
     488            pairedRelations.stream().filter(pair -> Boolean.TRUE.equals(pair.b)).map(pair -> pair.a)
     489                    .forEach(primitivesToDelete::add);
    459490        }
    460491
    461492        // build the delete command
  • src/org/openstreetmap/josm/gui/dialogs/DeleteFromRelationConfirmationDialog.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/DeleteFromRelationConfirmationDialog.java b/src/org/openstreetmap/josm/gui/dialogs/DeleteFromRelationConfirmationDialog.java
    index 692347ac8e..3b06ce2c20 100644
    a b import static org.openstreetmap.josm.tools.I18n.trn;  
    88import java.awt.BorderLayout;
    99import java.awt.Dimension;
    1010import java.awt.FlowLayout;
     11import java.awt.GridBagLayout;
    1112import java.awt.event.ActionEvent;
    1213import java.awt.event.WindowAdapter;
    1314import java.awt.event.WindowEvent;
    import javax.swing.table.TableColumn;  
    3435import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
    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;
    3941import org.openstreetmap.josm.gui.PrimitiveRenderer;
    import org.openstreetmap.josm.gui.help.HelpUtil;  
    4244import org.openstreetmap.josm.gui.util.GuiHelper;
    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/**
    4954 * This dialog is used to get a user confirmation that a collection of primitives can be removed
    public class DeleteFromRelationConfirmationDialog extends JDialog implements Tab  
    6873
    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;
    7381    private final JButton btnOK = new JButton(new OKAction());
    7482
    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(e -> deletedRelationsModelTableScrollPane.setVisible(this.deletedRelationsModel.getRowCount() > 0));
     90        // Default to not visible
     91        deletedRelationsModelTableScrollPane.setVisible(false);
     92        pnl.add(deletedRelationsModelTableScrollPane, GBC.eol().fill());
    7993        return pnl;
    8094    }
    8195
    public class DeleteFromRelationConfirmationDialog extends JDialog implements Tab  
    91105    protected final void build() {
    92106        model = new RelationMemberTableModel();
    93107        model.addTableModelListener(this);
     108        this.deletedRelationsModel = new RelationDeleteModel();
     109        this.deletedRelationsModel.addTableModelListener(this);
    94110        getContentPane().setLayout(new BorderLayout());
    95111        getContentPane().add(htmlPanel, BorderLayout.NORTH);
    96112        getContentPane().add(buildRelationMemberTablePanel(), BorderLayout.CENTER);
    public class DeleteFromRelationConfirmationDialog extends JDialog implements Tab  
    102118    }
    103119
    104120    protected void updateMessage() {
    105         int numObjectsToDelete = model.getNumObjectsToDelete();
    106         int numParentRelations = model.getNumParentRelations();
     121        int numObjectsToDelete = this.model.getNumObjectsToDelete() + this.deletedRelationsModel.getNumObjectsToDelete();
     122        int numParentRelations = this.model.getNumParentRelations() + this.deletedRelationsModel.getNumParentRelations();
    107123        final String msg1 = trn(
    108124                "Please confirm to remove <strong>{0} object</strong>.",
    109125                "Please confirm to remove <strong>{0} objects</strong>.",
    public class DeleteFromRelationConfirmationDialog extends JDialog implements Tab  
    119135    }
    120136
    121137    protected void updateTitle() {
    122         int numObjectsToDelete = model.getNumObjectsToDelete();
     138        int numObjectsToDelete = this.model.getNumObjectsToDelete() + this.deletedRelationsModel.getNumObjectsToDelete();
    123139        if (numObjectsToDelete > 0) {
    124140            setTitle(trn("Deleting {0} object", "Deleting {0} objects", numObjectsToDelete, numObjectsToDelete));
    125141        } else {
    public class DeleteFromRelationConfirmationDialog extends JDialog implements Tab  
    144160        return model;
    145161    }
    146162
     163    /**
     164     * Replies the data model used for relations that should probably be deleted.
     165     * @return the data model
     166     * @since xxx
     167     */
     168    public RelationDeleteModel getDeletedRelationsModel() {
     169        return this.deletedRelationsModel;
     170    }
     171
    147172    /**
    148173     * Replies true if the dialog was canceled
    149174     *
    public class DeleteFromRelationConfirmationDialog extends JDialog implements Tab  
    173198                new WindowGeometry(this).remember(getClass().getName() + ".geometry");
    174199            }
    175200            model.data.clear();
     201            this.deletedRelationsModel.data.clear();
    176202        }
    177203        super.setVisible(visible);
    178204    }
    public class DeleteFromRelationConfirmationDialog extends JDialog implements Tab  
    325351        }
    326352    }
    327353
     354    /**
     355     * The table model which manages relations that will be deleted, if their children are deleted.
     356     * @since xxx
     357     */
     358    public static class RelationDeleteModel extends DefaultTableModel {
     359        private final transient List<Pair<Relation, Boolean>> data = new ArrayList<>();
     360
     361        @Override
     362        public int getRowCount() {
     363            // This is called in the super constructor. Before we have instantiated the list. Removing the null check
     364            // WILL LEAD TO A SILENT NPE!
     365            if (this.data == null) {
     366                return 0;
     367            }
     368            return this.data.size();
     369        }
     370
     371        /**
     372         * Sets the data that should be displayed in the list.
     373         * @param references A list of references to display
     374         */
     375        public void populate(Collection<Pair<Relation, Boolean>> references) {
     376            this.data.clear();
     377            if (references != null) {
     378                this.data.addAll(references);
     379            }
     380            this.data.sort(Comparator.comparing(pair -> pair.a));
     381            fireTableDataChanged();
     382        }
     383
     384        /**
     385         * Gets the list of children that are currently displayed.
     386         * @return The children.
     387         */
     388        public Set<Relation> getObjectsToDelete() {
     389            return this.data.stream().filter(relation -> relation.b).map(relation -> relation.a).collect(Collectors.toSet());
     390        }
     391
     392        /**
     393         * Gets the number of elements {@link #getObjectsToDelete()} would return.
     394         * @return That number.
     395         */
     396        public int getNumObjectsToDelete() {
     397            return getObjectsToDelete().size();
     398        }
     399
     400        /**
     401         * Gets the set of parent relations
     402         * @return All parent relations of the references
     403         */
     404        public Set<OsmPrimitive> getParentRelations() {
     405            return this.data.stream()
     406                    .flatMap(pair -> Utils.filteredCollection(pair.a.getReferrers(), Relation.class).stream())
     407                    .collect(Collectors.toSet());
     408        }
     409
     410        /**
     411         * Gets the number of elements {@link #getParentRelations()} would return.
     412         * @return That number.
     413         */
     414        public int getNumParentRelations() {
     415            return getParentRelations().size();
     416        }
     417
     418        @Override
     419        public Object getValueAt(int rowIndex, int columnIndex) {
     420            if (this.data.isEmpty()) {
     421                return null;
     422            }
     423            Pair<Relation, Boolean> ref = this.data.get(rowIndex);
     424            switch(columnIndex) {
     425            case 0: return ref.a;
     426            case 1: return ref.b;
     427            default:
     428                assert false : "Illegal column index";
     429            }
     430            return null;
     431        }
     432
     433        @Override
     434        public boolean isCellEditable(int row, int column) {
     435            return !this.data.isEmpty() && column == 1;
     436        }
     437
     438        @Override
     439        public void setValueAt(Object aValue, int row, int column) {
     440            if (this.data.size() > row && column == 1 && aValue instanceof Boolean) {
     441                this.data.get(row).b = ((Boolean) aValue);
     442            }
     443        }
     444
     445        @Override
     446        public Class<?> getColumnClass(int columnIndex) {
     447            switch (columnIndex) {
     448            case 0:
     449                return Relation.class;
     450            case 1:
     451                return Boolean.class;
     452            default:
     453                return super.getColumnClass(columnIndex);
     454            }
     455        }
     456    }
     457
     458    private static class RelationDeleteTableColumnModel extends DefaultTableColumnModel {
     459        protected final void createColumns() {
     460            // column 0 - To Delete
     461            TableColumn col = new TableColumn(0);
     462            col.setHeaderValue(tr("Relation"));
     463            col.setResizable(true);
     464            col.setWidth(100);
     465            col.setPreferredWidth(100);
     466            col.setCellRenderer(new PrimitiveRenderer());
     467            addColumn(col);
     468
     469            // column 0 - From Relation
     470            col = new TableColumn(1);
     471            final String toDelete = tr("To delete");
     472            col.setHeaderValue(toDelete);
     473            col.setResizable(true);
     474            col.setPreferredWidth(toDelete.length());
     475            addColumn(col);
     476        }
     477
     478        RelationDeleteTableColumnModel() {
     479            createColumns();
     480        }
     481    }
     482
    328483    class OKAction extends AbstractAction {
    329484        OKAction() {
    330485            putValue(NAME, tr("OK"));