Ticket #21951: 21951.patch

File 21951.patch, 10.6 KB (added by taylor.smock, 4 years ago)

Validate relations and show user results in relation editing window. Note: Depends upon and partially conflicts with attachment:21825.6.patch:ticket:21825

  • src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java

    diff --git a/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java b/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java
    index 959f73d1d0..1c10a7c575 100644
    a b public class RelationChecker extends Test implements TaggingPresetListener {  
    154154                    .message(tr("Route scheme is unspecified. Add {0} ({1}=public_transport; {2}=legacy)", "public_transport:version", "2", "1"))
    155155                    .primitives(n)
    156156                    .build());
    157         } else if (n.hasKey("type") && allroles.isEmpty()) {
     157        } else if ((n.hasKey("type") && allroles.isEmpty()) || !n.hasKey("type")) {
    158158            errors.add(TestError.builder(this, Severity.OTHER, RELATION_UNKNOWN)
    159159                    .message(tr("Relation type is unknown"))
    160160                    .primitives(n)
  • src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java b/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
    index 743e05f4e0..c5e10ad531 100644
    a b package org.openstreetmap.josm.gui.dialogs.relation;  
    33
    44import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
    55import static org.openstreetmap.josm.tools.I18n.tr;
     6import static org.openstreetmap.josm.tools.I18n.trn;
    67
    78import java.awt.BorderLayout;
    89import java.awt.Component;
    import java.util.ArrayList;  
    2627import java.util.Arrays;
    2728import java.util.Collection;
    2829import java.util.Collections;
     30import java.util.Comparator;
    2931import java.util.EnumSet;
    3032import java.util.List;
    3133import java.util.Set;
     34import java.util.concurrent.Future;
     35import java.util.concurrent.atomic.AtomicReference;
    3236import java.util.stream.Collectors;
    3337
    3438import javax.swing.AbstractAction;
    import javax.swing.JTabbedPane;  
    4751import javax.swing.JTable;
    4852import javax.swing.JToolBar;
    4953import javax.swing.KeyStroke;
     54import javax.swing.event.TableModelListener;
     55import javax.swing.table.TableModel;
    5056
    5157import org.openstreetmap.josm.actions.JosmAction;
    5258import org.openstreetmap.josm.command.ChangeMembersCommand;
    import org.openstreetmap.josm.command.Command;  
    5460import org.openstreetmap.josm.data.UndoRedoHandler;
    5561import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener;
    5662import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
     63import org.openstreetmap.josm.data.osm.IRelation;
    5764import org.openstreetmap.josm.data.osm.OsmPrimitive;
    5865import org.openstreetmap.josm.data.osm.Relation;
    5966import org.openstreetmap.josm.data.osm.RelationMember;
    6067import org.openstreetmap.josm.data.osm.Tag;
     68import org.openstreetmap.josm.data.validation.TestError;
    6169import org.openstreetmap.josm.data.validation.tests.RelationChecker;
    6270import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    6371import org.openstreetmap.josm.gui.MainApplication;
    import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;  
    105113import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    106114import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
    107115import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
     116import org.openstreetmap.josm.gui.util.GuiHelper;
    108117import org.openstreetmap.josm.gui.util.WindowGeometry;
    109118import org.openstreetmap.josm.spi.preferences.Config;
    110119import org.openstreetmap.josm.tools.CheckParameterUtil;
     120import org.openstreetmap.josm.tools.GBC;
     121import org.openstreetmap.josm.tools.ImageProvider;
    111122import org.openstreetmap.josm.tools.InputMapUtils;
    112123import org.openstreetmap.josm.tools.Logging;
    113124import org.openstreetmap.josm.tools.Shortcut;
    public class GenericRelationEditor extends RelationEditor implements CommandQueu  
    132143    private final SelectionTableModel selectionTableModel;
    133144
    134145    private final AutoCompletingTextField tfRole;
     146    private final RelationEditorActionAccess actionAccess;
    135147
    136148    /**
    137149     * the menu item in the windows menu. Required to properly hide on dialog close.
    public class GenericRelationEditor extends RelationEditor implements CommandQueu  
    262274            selectedTabPane = sourceTabbedPane.getSelectedComponent();
    263275        });
    264276
    265         IRelationEditorActionAccess actionAccess = new RelationEditorActionAccess();
     277        actionAccess = new RelationEditorActionAccess();
    266278
    267279        refreshAction = new RefreshAction(actionAccess);
    268280        applyAction = new ApplyAction(actionAccess);
    269281        selectAction = new SelectAction(actionAccess);
    270282        duplicateAction = new DuplicateRelationAction(actionAccess);
    271283        deleteAction = new DeleteCurrentRelationAction(actionAccess);
     284
     285        this.memberTableModel.addTableModelListener(applyAction);
     286        this.tagEditorPanel.getModel().addTableModelListener(l -> {
     287            // Short circuit -- the tag editor will fire an update event on selection change, even if nothing actually
     288            // changed.
     289            if (l != null && l.getSource() instanceof TagEditorModel && !((TagEditorModel) l.getSource()).isDirty()) {
     290                return;
     291            }
     292            applyAction.tableChanged(l);
     293        });
     294
    272295        addPropertyChangeListener(deleteAction);
    273296
    274297        okAction = new OKAction(actionAccess);
    public class GenericRelationEditor extends RelationEditor implements CommandQueu  
    276299
    277300        getContentPane().add(buildToolBar(refreshAction, applyAction, selectAction, duplicateAction, deleteAction), BorderLayout.NORTH);
    278301        getContentPane().add(tabbedPane, BorderLayout.CENTER);
    279         getContentPane().add(buildOkCancelButtonPanel(okAction, cancelAction), BorderLayout.SOUTH);
     302        getContentPane().add(buildOkCancelButtonPanel(okAction, deleteAction, cancelAction), BorderLayout.SOUTH);
    280303
    281304        setSize(findMaxDialogSize());
    282305
    public class GenericRelationEditor extends RelationEditor implements CommandQueu  
    407430     *
    408431     * @return the panel with the OK and the Cancel button
    409432     */
    410     protected static JPanel buildOkCancelButtonPanel(OKAction okAction, CancelAction cancelAction) {
    411         JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
    412         pnl.add(new JButton(okAction));
     433    protected JPanel buildOkCancelButtonPanel(OKAction okAction, DeleteCurrentRelationAction deleteAction,
     434            CancelAction cancelAction) {
     435        JPanel mainPanel = new JPanel(new GridBagLayout());
     436        final JLabel errorLabel = new JLabel(" ");
     437        final JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
     438        mainPanel.add(errorLabel, GBC.eol().anchor(GridBagConstraints.CENTER));
     439        mainPanel.add(pnl, GBC.eol().anchor(GridBagConstraints.CENTER));
     440        final JButton okButton = new JButton(okAction);
     441        final JButton deleteButton = new JButton(deleteAction);
     442        okButton.setPreferredSize(deleteButton.getPreferredSize());
     443        pnl.add(okButton);
     444        pnl.add(deleteButton);
    413445        pnl.add(new JButton(cancelAction));
    414446        pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Dialog/RelationEditor"))));
    415         return pnl;
     447        // Keep users from saving invalid relations -- a relation MUST have at least a tag with the key "type"
     448        // AND must contain at least one other OSM object.
     449        final AtomicReference<Future<?>> testErrorsUpdate = new AtomicReference<>();
     450        final TableModelListener listener = l -> {
     451            final IRelation<?> newRelation = this.actionAccess.getChangedRelation();
     452            updateOkPanel(newRelation, okButton, deleteButton);
     453            final Future<?> oldFuture = testErrorsUpdate.getAndSet(MainApplication.worker.submit(() -> {
     454                GuiHelper.runInEDTAndWait(() -> errorLabel.setText(tr("Validation in progress")));
     455                final Collection<TestError> testErrors = newRelation.validate();
     456                GuiHelper.runInEDTAndWait(() -> updateErrorMessage(testErrors, errorLabel));
     457            }));
     458            if (oldFuture != null) {
     459                oldFuture.cancel(true);
     460            }
     461        };
     462        listener.tableChanged(null);
     463        this.memberTableModel.addTableModelListener(listener);
     464        this.tagEditorPanel.getModel().addTableModelListener(l -> {
     465            // Short circuit -- the tag editor will fire an update event on selection change, even if nothing actually
     466            // changed.
     467            if (l != null && l.getSource() instanceof TagEditorModel && !((TagEditorModel) l.getSource()).isDirty()) {
     468                return;
     469            }
     470            listener.tableChanged(l);
     471        });
     472        return mainPanel;
     473    }
     474
     475    /**
     476     * Update the OK panel area
     477     * @param newRelation What the new relation would "look" like if it were to be saved now
     478     * @param okButton The OK button
     479     * @param deleteButton The delete button
     480     */
     481    private void updateOkPanel(IRelation<?> newRelation, JButton okButton, JButton deleteButton) {
     482        okButton.setVisible(newRelation.isUseful() || this.getRelationSnapshot() == null);
     483        deleteButton.setVisible(!newRelation.isUseful() && this.getRelationSnapshot() != null);
     484        if (this.getRelationSnapshot() == null && !newRelation.isUseful()) {
     485            okButton.setText(tr("Delete"));
     486        } else {
     487            okButton.setText(tr("OK"));
     488        }
     489    }
     490
     491    /**
     492     * Update the error message
     493     * @param newRelation What the new relation would "look" like if it were to be saved now
     494     * @param errorLabel The label to update
     495     */
     496    private void updateErrorMessage(Collection<TestError> errors, JLabel errorLabel) {
     497        if (errors.isEmpty()) {
     498            // Setting " " helps keep the label in place for layout calculations
     499            errorLabel.setText(" ");
     500            errorLabel.setIcon(null);
     501        } else {
     502            final TestError error = errors.stream()
     503                    .min(Comparator.comparingInt(testError -> testError.getSeverity().getLevel()))
     504                    .orElse(errors.iterator().next());
     505            final StringBuilder sb = new StringBuilder();
     506            if (error.getDescription() != null) {
     507                sb.append(error.getDescription()).append(": ");
     508            }
     509            sb.append(error.getMessage());
     510            if (errors.size() > 1) {
     511                sb.append(trn(" with {0} more problem", " with {0} more problems", errors.size() - 1, errors.size() - 1));
     512            }
     513            errorLabel.setIcon(ImageProvider.get("data", error.getSeverity().getIcon()));
     514            errorLabel.setText(sb.toString());
     515        }
    416516    }
    417517
    418518    /**