Ignore:
Timestamp:
2016-01-17T02:54:22+01:00 (8 years ago)
Author:
Don-vip
Message:

massive refactoring of GenericRelationEditor. As JDialog cannot be instantiated in headless mode, extract all actions to separate classes in new package gui.dialogs.relation.actions in order to test them with JUnit

Location:
trunk/src/org/openstreetmap/josm/gui
Files:
29 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java

    r8958 r9496  
    55
    66import java.awt.Component;
     7import java.awt.GraphicsEnvironment;
    78import java.awt.GridBagLayout;
    8 import java.awt.HeadlessException;
    99import java.util.HashMap;
    1010import java.util.HashSet;
     
    113113     * @param defaultOption the default option; only meaningful if options is used; can be null
    114114     *
    115      * @return the option selected by user. {@link JOptionPane#CLOSED_OPTION} if the dialog was closed.
    116      * @throws HeadlessException if <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
     115     * @return the option selected by user.
     116     *         {@link JOptionPane#CLOSED_OPTION} if the dialog was closed.
     117     *         {@link JOptionPane#YES_OPTION} if <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
    117118     */
    118119    public static int showOptionDialog(String preferenceKey, Component parent, Object message, String title, int optionType,
    119             int messageType, Object[] options, Object defaultOption) throws HeadlessException {
     120            int messageType, Object[] options, Object defaultOption) {
    120121        int ret = getDialogReturnValue(preferenceKey);
    121122        if (isYesOrNo(ret))
    122123            return ret;
    123124        MessagePanel pnl = new MessagePanel(message, isInBulkOperation(preferenceKey));
    124         ret = JOptionPane.showOptionDialog(parent, pnl, title, optionType, messageType, null, options, defaultOption);
     125        if (GraphicsEnvironment.isHeadless()) {
     126            // for unit tests
     127            ret = JOptionPane.YES_OPTION;
     128        } else {
     129            ret = JOptionPane.showOptionDialog(parent, pnl, title, optionType, messageType, null, options, defaultOption);
     130        }
    125131        if (isYesOrNo(ret)) {
    126132            pnl.getNotShowAgain().store(preferenceKey, ret);
     
    151157     * @param optionType  the option type
    152158     * @param messageType the message type
    153      * @param trueOption  if this option is selected the method replies true
     159     * @param trueOption if this option is selected the method replies true
    154160     *
    155161     *
    156162     * @return true, if the selected option is equal to <code>trueOption</code>, otherwise false.
    157      * @throws HeadlessException if <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
     163     *         {@code trueOption} if <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
    158164     *
    159165     * @see JOptionPane#INFORMATION_MESSAGE
     
    162168     */
    163169    public static boolean showConfirmationDialog(String preferenceKey, Component parent, Object message, String title,
    164             int optionType, int messageType, int trueOption) throws HeadlessException {
     170            int optionType, int messageType, int trueOption) {
    165171        int ret = getDialogReturnValue(preferenceKey);
    166172        if (isYesOrNo(ret))
    167173            return ret == trueOption;
    168174        MessagePanel pnl = new MessagePanel(message, isInBulkOperation(preferenceKey));
    169         ret = JOptionPane.showConfirmDialog(parent, pnl, title, optionType, messageType);
     175        if (GraphicsEnvironment.isHeadless()) {
     176            // for unit tests
     177            ret = trueOption;
     178        } else {
     179            ret = JOptionPane.showConfirmDialog(parent, pnl, title, optionType, messageType);
     180        }
    170181        if (isYesOrNo(ret)) {
    171182            pnl.getNotShowAgain().store(preferenceKey, ret);
  • trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java

    r9438 r9496  
    44import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
    55import static org.openstreetmap.josm.tools.I18n.tr;
    6 import static org.openstreetmap.josm.tools.I18n.trn;
    76
    87import java.awt.BorderLayout;
     
    2120import java.awt.event.WindowAdapter;
    2221import java.awt.event.WindowEvent;
    23 import java.beans.PropertyChangeEvent;
    24 import java.beans.PropertyChangeListener;
    2522import java.util.ArrayList;
    2623import java.util.Collection;
     
    4643import javax.swing.JToolBar;
    4744import javax.swing.KeyStroke;
    48 import javax.swing.SwingUtilities;
    4945import javax.swing.event.ChangeEvent;
    5046import javax.swing.event.ChangeListener;
    51 import javax.swing.event.DocumentEvent;
    52 import javax.swing.event.DocumentListener;
    5347import javax.swing.event.ListSelectionEvent;
    5448import javax.swing.event.ListSelectionListener;
    55 import javax.swing.event.TableModelEvent;
    56 import javax.swing.event.TableModelListener;
    5749
    5850import org.openstreetmap.josm.Main;
    59 import org.openstreetmap.josm.actions.CopyAction;
    6051import org.openstreetmap.josm.actions.ExpertToggleAction;
    6152import org.openstreetmap.josm.actions.JosmAction;
    62 import org.openstreetmap.josm.command.AddCommand;
    6353import org.openstreetmap.josm.command.ChangeCommand;
    6454import org.openstreetmap.josm.command.Command;
    65 import org.openstreetmap.josm.command.conflict.ConflictAddCommand;
    66 import org.openstreetmap.josm.data.conflict.Conflict;
    67 import org.openstreetmap.josm.data.osm.DataSet;
    6855import org.openstreetmap.josm.data.osm.OsmPrimitive;
    69 import org.openstreetmap.josm.data.osm.PrimitiveData;
    7056import org.openstreetmap.josm.data.osm.Relation;
    7157import org.openstreetmap.josm.data.osm.RelationMember;
     
    7359import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    7460import org.openstreetmap.josm.gui.DefaultNameFormatter;
    75 import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    76 import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
    7761import org.openstreetmap.josm.gui.MainMenu;
    7862import org.openstreetmap.josm.gui.SideButton;
     63import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAfterSelection;
     64import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtEndAction;
     65import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtStartAction;
     66import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedBeforeSelection;
     67import org.openstreetmap.josm.gui.dialogs.relation.actions.ApplyAction;
     68import org.openstreetmap.josm.gui.dialogs.relation.actions.CancelAction;
     69import org.openstreetmap.josm.gui.dialogs.relation.actions.CopyMembersAction;
     70import org.openstreetmap.josm.gui.dialogs.relation.actions.DeleteCurrentRelationAction;
     71import org.openstreetmap.josm.gui.dialogs.relation.actions.DownloadIncompleteMembersAction;
     72import org.openstreetmap.josm.gui.dialogs.relation.actions.DownloadSelectedIncompleteMembersAction;
     73import org.openstreetmap.josm.gui.dialogs.relation.actions.DuplicateRelationAction;
     74import org.openstreetmap.josm.gui.dialogs.relation.actions.EditAction;
     75import org.openstreetmap.josm.gui.dialogs.relation.actions.MoveDownAction;
     76import org.openstreetmap.josm.gui.dialogs.relation.actions.MoveUpAction;
     77import org.openstreetmap.josm.gui.dialogs.relation.actions.OKAction;
     78import org.openstreetmap.josm.gui.dialogs.relation.actions.PasteMembersAction;
     79import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveAction;
     80import org.openstreetmap.josm.gui.dialogs.relation.actions.RemoveSelectedAction;
     81import org.openstreetmap.josm.gui.dialogs.relation.actions.ReverseAction;
     82import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectPrimitivesForSelectedMembersAction;
     83import org.openstreetmap.josm.gui.dialogs.relation.actions.SelectedMembersForSelectionAction;
     84import org.openstreetmap.josm.gui.dialogs.relation.actions.SetRoleAction;
     85import org.openstreetmap.josm.gui.dialogs.relation.actions.SortAction;
     86import org.openstreetmap.josm.gui.dialogs.relation.actions.SortBelowAction;
    7987import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
    8088import org.openstreetmap.josm.gui.help.HelpUtil;
    8189import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    82 import org.openstreetmap.josm.gui.tagging.TagEditorModel;
    8390import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
    8491import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
     
    8895import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
    8996import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    90 import org.openstreetmap.josm.io.OnlineResource;
    9197import org.openstreetmap.josm.tools.CheckParameterUtil;
    92 import org.openstreetmap.josm.tools.ImageProvider;
    9398import org.openstreetmap.josm.tools.Shortcut;
    9499import org.openstreetmap.josm.tools.WindowGeometry;
     
    119124    private JMenuItem windowMenuItem;
    120125    /**
    121      * Button for performing the {@link org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.SortBelowAction}.
     126     * Button for performing the {@link org.openstreetmap.josm.gui.dialogs.relation.actions.SortBelowAction}.
    122127     */
    123128    private JButton sortBelowButton;
     
    134139     */
    135140    public GenericRelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) {
    136         super(layer, relation, selectedMembers);
     141        super(layer, relation);
    137142
    138143        setRememberWindowGeometry(getClass().getName() + ".geometry",
     
    227232                Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")), KeyEvent.VK_V, Shortcut.CTRL_SHIFT).getKeyStroke());
    228233                // CHECKSTYLE.ON: LineLength
    229         registerCopyPasteAction(new PasteMembersAction(), "PASTE_MEMBERS", Shortcut.getPasteKeyStroke());
    230         registerCopyPasteAction(new CopyMembersAction(), "COPY_MEMBERS", Shortcut.getCopyKeyStroke());
     234
     235        registerCopyPasteAction(new PasteMembersAction(memberTableModel, getLayer(), this) {
     236            @Override
     237            public void actionPerformed(ActionEvent e) {
     238                super.actionPerformed(e);
     239                tfRole.requestFocusInWindow();
     240            }
     241        }, "PASTE_MEMBERS", Shortcut.getPasteKeyStroke());
     242
     243        registerCopyPasteAction(new CopyMembersAction(memberTableModel, getLayer(), this),
     244                "COPY_MEMBERS", Shortcut.getCopyKeyStroke());
    231245
    232246        tagEditorPanel.setNextFocusComponent(memberTable);
     
    244258        JToolBar tb  = new JToolBar();
    245259        tb.setFloatable(false);
    246         tb.add(new ApplyAction());
    247         tb.add(new DuplicateRelationAction());
    248         DeleteCurrentRelationAction deleteAction = new DeleteCurrentRelationAction();
     260        tb.add(new ApplyAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this));
     261        tb.add(new DuplicateRelationAction(memberTableModel, tagEditorPanel.getModel(), getLayer()));
     262        DeleteCurrentRelationAction deleteAction = new DeleteCurrentRelationAction(getLayer(), this);
    249263        addPropertyChangeListener(deleteAction);
    250264        tb.add(deleteAction);
     
    258272     */
    259273    protected JPanel buildOkCancelButtonPanel() {
    260         JPanel pnl = new JPanel();
    261         pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
    262 
    263         pnl.add(new SideButton(new OKAction()));
    264         pnl.add(new SideButton(new CancelAction()));
     274        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
     275        pnl.add(new SideButton(new OKAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this, tfRole)));
     276        pnl.add(new SideButton(new CancelAction(memberTable, memberTableModel, tagEditorPanel.getModel(), getLayer(), this, tfRole)));
    265277        pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Dialog/RelationEditor"))));
    266278        return pnl;
     
    273285     */
    274286    protected JPanel buildTagEditorPanel() {
    275         JPanel pnl = new JPanel();
    276         pnl.setLayout(new GridBagLayout());
     287        JPanel pnl = new JPanel(new GridBagLayout());
    277288
    278289        GridBagConstraints gc = new GridBagConstraints();
     
    366377        tfRole.setText(Main.pref.get("relation.editor.generic.lastrole", ""));
    367378        p3.add(tfRole);
    368         SetRoleAction setRoleAction = new SetRoleAction();
     379        SetRoleAction setRoleAction = new SetRoleAction(memberTable, memberTableModel, tfRole);
    369380        memberTableModel.getSelectionModel().addListSelectionListener(setRoleAction);
    370381        tfRole.getDocument().addDocumentListener(setRoleAction);
     
    488499
    489500        // -- move up action
    490         MoveUpAction moveUpAction = new MoveUpAction();
     501        MoveUpAction moveUpAction = new MoveUpAction(memberTable, memberTableModel, "moveUp");
    491502        memberTableModel.getSelectionModel().addListSelectionListener(moveUpAction);
    492503        tb.add(moveUpAction);
    493         memberTable.getActionMap().put("moveUp", moveUpAction);
    494504
    495505        // -- move down action
    496         MoveDownAction moveDownAction = new MoveDownAction();
     506        MoveDownAction moveDownAction = new MoveDownAction(memberTable, memberTableModel, "moveDown");
    497507        memberTableModel.getSelectionModel().addListSelectionListener(moveDownAction);
    498508        tb.add(moveDownAction);
    499         memberTable.getActionMap().put("moveDown", moveDownAction);
    500509
    501510        tb.addSeparator();
    502511
    503512        // -- edit action
    504         EditAction editAction = new EditAction();
     513        EditAction editAction = new EditAction(memberTable, memberTableModel, getLayer());
    505514        memberTableModel.getSelectionModel().addListSelectionListener(editAction);
    506515        tb.add(editAction);
    507516
    508517        // -- delete action
    509         RemoveAction removeSelectedAction = new RemoveAction();
     518        RemoveAction removeSelectedAction = new RemoveAction(memberTable, memberTableModel, "removeSelected");
    510519        memberTable.getSelectionModel().addListSelectionListener(removeSelectedAction);
    511520        tb.add(removeSelectedAction);
    512         memberTable.getActionMap().put("removeSelected", removeSelectedAction);
    513521
    514522        tb.addSeparator();
    515523        // -- sort action
    516         SortAction sortAction = new SortAction();
     524        SortAction sortAction = new SortAction(memberTable, memberTableModel);
    517525        memberTableModel.addTableModelListener(sortAction);
    518526        tb.add(sortAction);
    519         final SortBelowAction sortBelowAction = new SortBelowAction();
     527        final SortBelowAction sortBelowAction = new SortBelowAction(memberTable, memberTableModel);
    520528        memberTableModel.addTableModelListener(sortBelowAction);
    521529        memberTableModel.getSelectionModel().addListSelectionListener(sortBelowAction);
     
    523531
    524532        // -- reverse action
    525         ReverseAction reverseAction = new ReverseAction();
     533        ReverseAction reverseAction = new ReverseAction(memberTable, memberTableModel);
    526534        memberTableModel.addTableModelListener(reverseAction);
    527535        tb.add(reverseAction);
     
    530538
    531539        // -- download action
    532         DownloadIncompleteMembersAction downloadIncompleteMembersAction = new DownloadIncompleteMembersAction();
     540        DownloadIncompleteMembersAction downloadIncompleteMembersAction = new DownloadIncompleteMembersAction(
     541                memberTable, memberTableModel, "downloadIncomplete", getLayer(), this);
    533542        memberTable.getModel().addTableModelListener(downloadIncompleteMembersAction);
    534543        tb.add(downloadIncompleteMembersAction);
    535         memberTable.getActionMap().put("downloadIncomplete", downloadIncompleteMembersAction);
    536544
    537545        // -- download selected action
    538         DownloadSelectedIncompleteMembersAction downloadSelectedIncompleteMembersAction = new DownloadSelectedIncompleteMembersAction();
     546        DownloadSelectedIncompleteMembersAction downloadSelectedIncompleteMembersAction = new DownloadSelectedIncompleteMembersAction(
     547                memberTable, memberTableModel, null, getLayer(), this);
    539548        memberTable.getModel().addTableModelListener(downloadSelectedIncompleteMembersAction);
    540549        memberTable.getSelectionModel().addListSelectionListener(downloadSelectedIncompleteMembersAction);
     
    560569
    561570        // -- add at start action
    562         AddSelectedAtStartAction addSelectionAction = new AddSelectedAtStartAction();
     571        AddSelectedAtStartAction addSelectionAction = new AddSelectedAtStartAction(
     572                memberTableModel, selectionTableModel, this);
    563573        selectionTableModel.addTableModelListener(addSelectionAction);
    564574        tb.add(addSelectionAction);
    565575
    566576        // -- add before selected action
    567         AddSelectedBeforeSelection addSelectedBeforeSelectionAction = new AddSelectedBeforeSelection();
     577        AddSelectedBeforeSelection addSelectedBeforeSelectionAction = new AddSelectedBeforeSelection(
     578                memberTableModel, selectionTableModel, this);
    568579        selectionTableModel.addTableModelListener(addSelectedBeforeSelectionAction);
    569580        memberTableModel.getSelectionModel().addListSelectionListener(addSelectedBeforeSelectionAction);
     
    571582
    572583        // -- add after selected action
    573         AddSelectedAfterSelection addSelectedAfterSelectionAction = new AddSelectedAfterSelection();
     584        AddSelectedAfterSelection addSelectedAfterSelectionAction = new AddSelectedAfterSelection(
     585                memberTableModel, selectionTableModel, this);
    574586        selectionTableModel.addTableModelListener(addSelectedAfterSelectionAction);
    575587        memberTableModel.getSelectionModel().addListSelectionListener(addSelectedAfterSelectionAction);
     
    577589
    578590        // -- add at end action
    579         AddSelectedAtEndAction addSelectedAtEndAction = new AddSelectedAtEndAction();
     591        AddSelectedAtEndAction addSelectedAtEndAction = new AddSelectedAtEndAction(
     592                memberTableModel, selectionTableModel, this);
    580593        selectionTableModel.addTableModelListener(addSelectedAtEndAction);
    581594        tb.add(addSelectedAtEndAction);
     
    584597
    585598        // -- select members action
    586         SelectedMembersForSelectionAction selectMembersForSelectionAction = new SelectedMembersForSelectionAction();
     599        SelectedMembersForSelectionAction selectMembersForSelectionAction = new SelectedMembersForSelectionAction(
     600                memberTableModel, selectionTableModel, getLayer());
    587601        selectionTableModel.addTableModelListener(selectMembersForSelectionAction);
    588602        memberTableModel.addTableModelListener(selectMembersForSelectionAction);
     
    590604
    591605        // -- select action
    592         SelectPrimitivesForSelectedMembersAction selectAction = new SelectPrimitivesForSelectedMembersAction();
     606        SelectPrimitivesForSelectedMembersAction selectAction = new SelectPrimitivesForSelectedMembersAction(
     607                memberTable, memberTableModel, getLayer());
    593608        memberTable.getSelectionModel().addListSelectionListener(selectAction);
    594609        tb.add(selectAction);
     
    597612
    598613        // -- remove selected action
    599         RemoveSelectedAction removeSelectedAction = new RemoveSelectedAction();
     614        RemoveSelectedAction removeSelectedAction = new RemoveSelectedAction(memberTableModel, selectionTableModel, getLayer());
    600615        selectionTableModel.addTableModelListener(removeSelectedAction);
    601616        tb.add(removeSelectedAction);
     
    708723    }
    709724
    710     static class AddAbortException extends Exception {
    711     }
    712 
    713     static boolean confirmAddingPrimitive(OsmPrimitive primitive) throws AddAbortException {
     725    /**
     726     * Exception thrown when user aborts add operation.
     727     */
     728    public static class AddAbortException extends Exception {
     729    }
     730
     731    /**
     732     * Asks confirmationbefore adding a primitive.
     733     * @param primitive primitive to add
     734     * @return {@code true} is user confirms the operation, {@code false} otherwise
     735     * @throws AddAbortException if user aborts operation
     736     */
     737    public static boolean confirmAddingPrimitive(OsmPrimitive primitive) throws AddAbortException {
    714738        String msg = tr("<html>This relation already has one or more members referring to<br>"
    715739                + "the object ''{0}''<br>"
     
    736760            return false;
    737761        case JOptionPane.CANCEL_OPTION:
     762        default:
    738763            throw new AddAbortException();
    739764        }
    740         // should not happen
    741         return false;
    742     }
    743 
    744     static void warnOfCircularReferences(OsmPrimitive primitive) {
     765    }
     766
     767    /**
     768     * Warn about circular references.
     769     * @param primitive the concerned primitive
     770     */
     771    public static void warnOfCircularReferences(OsmPrimitive primitive) {
    745772        String msg = tr("<html>You are trying to add a relation to itself.<br>"
    746773                + "<br>"
     
    800827    }
    801828
    802     abstract class AddFromSelectionAction extends AbstractAction {
    803         protected boolean isPotentialDuplicate(OsmPrimitive primitive) {
    804             return memberTableModel.hasMembersReferringTo(Collections.singleton(primitive));
    805         }
    806 
    807         protected List<OsmPrimitive> filterConfirmedPrimitives(List<OsmPrimitive> primitives) throws AddAbortException {
    808             if (primitives == null || primitives.isEmpty())
    809                 return primitives;
    810             List<OsmPrimitive> ret = new ArrayList<>();
    811             ConditionalOptionPaneUtil.startBulkOperation("add_primitive_to_relation");
    812             for (OsmPrimitive primitive : primitives) {
    813                 if (primitive instanceof Relation && getRelation() != null && getRelation().equals(primitive)) {
    814                     warnOfCircularReferences(primitive);
    815                     continue;
    816                 }
    817                 if (isPotentialDuplicate(primitive)) {
    818                     if (confirmAddingPrimitive(primitive)) {
    819                         ret.add(primitive);
    820                     }
    821                     continue;
    822                 } else {
    823                     ret.add(primitive);
    824                 }
    825             }
    826             ConditionalOptionPaneUtil.endBulkOperation("add_primitive_to_relation");
    827             return ret;
    828         }
    829     }
    830 
    831     class AddSelectedAtStartAction extends AddFromSelectionAction implements TableModelListener {
    832         AddSelectedAtStartAction() {
    833             putValue(SHORT_DESCRIPTION,
    834                     tr("Add all objects selected in the current dataset before the first member"));
    835             putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copystartright"));
    836             refreshEnabled();
    837         }
    838 
    839         protected void refreshEnabled() {
    840             setEnabled(selectionTableModel.getRowCount() > 0);
    841         }
    842 
    843         @Override
    844         public void actionPerformed(ActionEvent e) {
    845             try {
    846                 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
    847                 memberTableModel.addMembersAtBeginning(toAdd);
    848             } catch (AddAbortException ex) {
    849                 // do nothing
    850                 if (Main.isTraceEnabled()) {
    851                     Main.trace(ex.getMessage());
    852                 }
    853             }
    854         }
    855 
    856         @Override
    857         public void tableChanged(TableModelEvent e) {
    858             refreshEnabled();
    859         }
    860     }
    861 
    862     class AddSelectedAtEndAction extends AddFromSelectionAction implements TableModelListener {
    863         AddSelectedAtEndAction() {
    864             putValue(SHORT_DESCRIPTION, tr("Add all objects selected in the current dataset after the last member"));
    865             putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyendright"));
    866             refreshEnabled();
    867         }
    868 
    869         protected void refreshEnabled() {
    870             setEnabled(selectionTableModel.getRowCount() > 0);
    871         }
    872 
    873         @Override
    874         public void actionPerformed(ActionEvent e) {
    875             try {
    876                 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
    877                 memberTableModel.addMembersAtEnd(toAdd);
    878             } catch (AddAbortException ex) {
    879                 // do nothing
    880                 if (Main.isTraceEnabled()) {
    881                     Main.trace(ex.getMessage());
    882                 }
    883             }
    884         }
    885 
    886         @Override
    887         public void tableChanged(TableModelEvent e) {
    888             refreshEnabled();
    889         }
    890     }
    891 
    892     class AddSelectedBeforeSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener {
    893         /**
    894          * Constructs a new {@code AddSelectedBeforeSelection}.
    895          */
    896         AddSelectedBeforeSelection() {
    897             putValue(SHORT_DESCRIPTION,
    898                     tr("Add all objects selected in the current dataset before the first selected member"));
    899             putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copybeforecurrentright"));
    900             refreshEnabled();
    901         }
    902 
    903         protected void refreshEnabled() {
    904             setEnabled(selectionTableModel.getRowCount() > 0
    905                     && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
    906         }
    907 
    908         @Override
    909         public void actionPerformed(ActionEvent e) {
    910             try {
    911                 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
    912                 memberTableModel.addMembersBeforeIdx(toAdd, memberTableModel
    913                         .getSelectionModel().getMinSelectionIndex());
    914             } catch (AddAbortException ex) {
    915                 // do nothing
    916                 if (Main.isTraceEnabled()) {
    917                     Main.trace(ex.getMessage());
    918                 }
    919             }
    920         }
    921 
    922         @Override
    923         public void tableChanged(TableModelEvent e) {
    924             refreshEnabled();
    925         }
    926 
    927         @Override
    928         public void valueChanged(ListSelectionEvent e) {
    929             refreshEnabled();
    930         }
    931     }
    932 
    933     class AddSelectedAfterSelection extends AddFromSelectionAction implements TableModelListener, ListSelectionListener {
    934         AddSelectedAfterSelection() {
    935             putValue(SHORT_DESCRIPTION,
    936                     tr("Add all objects selected in the current dataset after the last selected member"));
    937             putValue(SMALL_ICON, ImageProvider.get("dialogs/conflict", "copyaftercurrentright"));
    938             refreshEnabled();
    939         }
    940 
    941         protected void refreshEnabled() {
    942             setEnabled(selectionTableModel.getRowCount() > 0
    943                     && memberTableModel.getSelectionModel().getMinSelectionIndex() >= 0);
    944         }
    945 
    946         @Override
    947         public void actionPerformed(ActionEvent e) {
    948             try {
    949                 List<OsmPrimitive> toAdd = filterConfirmedPrimitives(selectionTableModel.getSelection());
    950                 memberTableModel.addMembersAfterIdx(toAdd, memberTableModel
    951                         .getSelectionModel().getMaxSelectionIndex());
    952             } catch (AddAbortException ex) {
    953                 // do nothing
    954                 if (Main.isTraceEnabled()) {
    955                     Main.trace(ex.getMessage());
    956                 }
    957             }
    958         }
    959 
    960         @Override
    961         public void tableChanged(TableModelEvent e) {
    962             refreshEnabled();
    963         }
    964 
    965         @Override
    966         public void valueChanged(ListSelectionEvent e) {
    967             refreshEnabled();
    968         }
    969     }
    970 
    971     class RemoveSelectedAction extends AbstractAction implements TableModelListener {
    972         /**
    973          * Constructs a new {@code RemoveSelectedAction}.
    974          */
    975         RemoveSelectedAction() {
    976             putValue(SHORT_DESCRIPTION, tr("Remove all members referring to one of the selected objects"));
    977             putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "deletemembers"));
    978             updateEnabledState();
    979         }
    980 
    981         protected void updateEnabledState() {
    982             DataSet ds = getLayer().data;
    983             if (ds == null || ds.getSelected().isEmpty()) {
    984                 setEnabled(false);
    985                 return;
    986             }
    987             // only enable the action if we have members referring to the
    988             // selected primitives
    989             //
    990             setEnabled(memberTableModel.hasMembersReferringTo(ds.getSelected()));
    991         }
    992 
    993         @Override
    994         public void actionPerformed(ActionEvent e) {
    995             memberTableModel.removeMembersReferringTo(selectionTableModel.getSelection());
    996         }
    997 
    998         @Override
    999         public void tableChanged(TableModelEvent e) {
    1000             updateEnabledState();
    1001         }
    1002     }
    1003 
    1004     /**
    1005      * Selects  members in the relation editor which refer to primitives in the current
    1006      * selection of the context layer.
    1007      *
    1008      */
    1009     class SelectedMembersForSelectionAction extends AbstractAction implements TableModelListener {
    1010         SelectedMembersForSelectionAction() {
    1011             putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection"));
    1012             putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectmembers"));
    1013             updateEnabledState();
    1014         }
    1015 
    1016         protected void updateEnabledState() {
    1017             boolean enabled = selectionTableModel.getRowCount() > 0
    1018             &&  !memberTableModel.getChildPrimitives(getLayer().data.getSelected()).isEmpty();
    1019 
    1020             if (enabled) {
    1021                 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to {0} objects in the current selection",
    1022                         memberTableModel.getChildPrimitives(getLayer().data.getSelected()).size()));
    1023             } else {
    1024                 putValue(SHORT_DESCRIPTION, tr("Select relation members which refer to objects in the current selection"));
    1025             }
    1026             setEnabled(enabled);
    1027         }
    1028 
    1029         @Override
    1030         public void actionPerformed(ActionEvent e) {
    1031             memberTableModel.selectMembersReferringTo(getLayer().data.getSelected());
    1032         }
    1033 
    1034         @Override
    1035         public void tableChanged(TableModelEvent e) {
    1036             updateEnabledState();
    1037         }
    1038     }
    1039 
    1040     /**
    1041      * Selects primitives in the layer this editor belongs to. The selected primitives are
    1042      * equal to the set of primitives the currently selected relation members refer to.
    1043      *
    1044      */
    1045     class SelectPrimitivesForSelectedMembersAction extends AbstractAction implements ListSelectionListener {
    1046         SelectPrimitivesForSelectedMembersAction() {
    1047             putValue(SHORT_DESCRIPTION, tr("Select objects for selected relation members"));
    1048             putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "selectprimitives"));
    1049             updateEnabledState();
    1050         }
    1051 
    1052         protected void updateEnabledState() {
    1053             setEnabled(memberTable.getSelectedRowCount() > 0);
    1054         }
    1055 
    1056         @Override
    1057         public void actionPerformed(ActionEvent e) {
    1058             getLayer().data.setSelected(memberTableModel.getSelectedChildPrimitives());
    1059         }
    1060 
    1061         @Override
    1062         public void valueChanged(ListSelectionEvent e) {
    1063             updateEnabledState();
    1064         }
    1065     }
    1066 
    1067     class SortAction extends AbstractAction implements TableModelListener {
    1068         SortAction() {
    1069             String tooltip = tr("Sort the relation members");
    1070             putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort"));
    1071             putValue(NAME, tr("Sort"));
    1072             Shortcut sc = Shortcut.registerShortcut("relationeditor:sort", tr("Relation Editor: Sort"),
    1073                 KeyEvent.VK_END, Shortcut.ALT);
    1074             sc.setAccelerator(this);
    1075             putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
    1076             updateEnabledState();
    1077         }
    1078 
    1079         @Override
    1080         public void actionPerformed(ActionEvent e) {
    1081             memberTableModel.sort();
    1082         }
    1083 
    1084         protected void updateEnabledState() {
    1085             setEnabled(memberTableModel.getRowCount() > 0);
    1086         }
    1087 
    1088         @Override
    1089         public void tableChanged(TableModelEvent e) {
    1090             updateEnabledState();
    1091         }
    1092     }
    1093 
    1094     class SortBelowAction extends AbstractAction implements TableModelListener, ListSelectionListener {
    1095         SortBelowAction() {
    1096             putValue(SMALL_ICON, ImageProvider.get("dialogs", "sort_below"));
    1097             putValue(NAME, tr("Sort below"));
    1098             putValue(SHORT_DESCRIPTION, tr("Sort the selected relation members and all members below"));
    1099             updateEnabledState();
    1100         }
    1101 
    1102         @Override
    1103         public void actionPerformed(ActionEvent e) {
    1104             memberTableModel.sortBelow();
    1105         }
    1106 
    1107         protected void updateEnabledState() {
    1108             setEnabled(memberTableModel.getRowCount() > 0 && !memberTableModel.getSelectionModel().isSelectionEmpty());
    1109         }
    1110 
    1111         @Override
    1112         public void tableChanged(TableModelEvent e) {
    1113             updateEnabledState();
    1114         }
    1115 
    1116         @Override
    1117         public void valueChanged(ListSelectionEvent e) {
    1118             updateEnabledState();
    1119         }
    1120     }
    1121 
    1122     class ReverseAction extends AbstractAction implements TableModelListener {
    1123         ReverseAction() {
    1124             putValue(SHORT_DESCRIPTION, tr("Reverse the order of the relation members"));
    1125             putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "reverse"));
    1126             putValue(NAME, tr("Reverse"));
    1127         //  Shortcut.register Shortcut("relationeditor:reverse", tr("Relation Editor: Reverse"), KeyEvent.VK_END, Shortcut.ALT)
    1128             updateEnabledState();
    1129         }
    1130 
    1131         @Override
    1132         public void actionPerformed(ActionEvent e) {
    1133             memberTableModel.reverse();
    1134         }
    1135 
    1136         protected void updateEnabledState() {
    1137             setEnabled(memberTableModel.getRowCount() > 0);
    1138         }
    1139 
    1140         @Override
    1141         public void tableChanged(TableModelEvent e) {
    1142             updateEnabledState();
    1143         }
    1144     }
    1145 
    1146     class MoveUpAction extends AbstractAction implements ListSelectionListener {
    1147         MoveUpAction() {
    1148             String tooltip = tr("Move the currently selected members up");
    1149             putValue(SMALL_ICON, ImageProvider.get("dialogs", "moveup"));
    1150             Shortcut sc = Shortcut.registerShortcut("relationeditor:moveup", tr("Relation Editor: Move Up"),
    1151                 KeyEvent.VK_UP, Shortcut.ALT);
    1152             sc.setAccelerator(this);
    1153             putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
    1154             setEnabled(false);
    1155         }
    1156 
    1157         @Override
    1158         public void actionPerformed(ActionEvent e) {
    1159             memberTableModel.moveUp(memberTable.getSelectedRows());
    1160         }
    1161 
    1162         @Override
    1163         public void valueChanged(ListSelectionEvent e) {
    1164             setEnabled(memberTableModel.canMoveUp(memberTable.getSelectedRows()));
    1165         }
    1166     }
    1167 
    1168     class MoveDownAction extends AbstractAction implements ListSelectionListener {
    1169         MoveDownAction() {
    1170             String tooltip = tr("Move the currently selected members down");
    1171             putValue(SMALL_ICON, ImageProvider.get("dialogs", "movedown"));
    1172             Shortcut sc = Shortcut.registerShortcut("relationeditor:movedown", tr("Relation Editor: Move Down"),
    1173                 KeyEvent.VK_DOWN, Shortcut.ALT);
    1174             sc.setAccelerator(this);
    1175             putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
    1176             setEnabled(false);
    1177         }
    1178 
    1179         @Override
    1180         public void actionPerformed(ActionEvent e) {
    1181             memberTableModel.moveDown(memberTable.getSelectedRows());
    1182         }
    1183 
    1184         @Override
    1185         public void valueChanged(ListSelectionEvent e) {
    1186             setEnabled(memberTableModel.canMoveDown(memberTable.getSelectedRows()));
    1187         }
    1188     }
    1189 
    1190     class RemoveAction extends AbstractAction implements ListSelectionListener {
    1191         RemoveAction() {
    1192             String tooltip = tr("Remove the currently selected members from this relation");
    1193             putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
    1194             putValue(NAME, tr("Remove"));
    1195             Shortcut sc = Shortcut.registerShortcut("relationeditor:remove", tr("Relation Editor: Remove"),
    1196                 KeyEvent.VK_DELETE, Shortcut.ALT);
    1197             sc.setAccelerator(this);
    1198             putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
    1199             setEnabled(false);
    1200         }
    1201 
    1202         @Override
    1203         public void actionPerformed(ActionEvent e) {
    1204             memberTableModel.remove(memberTable.getSelectedRows());
    1205         }
    1206 
    1207         @Override
    1208         public void valueChanged(ListSelectionEvent e) {
    1209             setEnabled(memberTableModel.canRemove(memberTable.getSelectedRows()));
    1210         }
    1211     }
    1212 
    1213     class DeleteCurrentRelationAction extends AbstractAction implements PropertyChangeListener {
    1214         DeleteCurrentRelationAction() {
    1215             putValue(SHORT_DESCRIPTION, tr("Delete the currently edited relation"));
    1216             putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
    1217             putValue(NAME, tr("Delete"));
    1218             updateEnabledState();
    1219         }
    1220 
    1221         public void run() {
    1222             Relation toDelete = getRelation();
    1223             if (toDelete == null)
    1224                 return;
    1225             org.openstreetmap.josm.actions.mapmode.DeleteAction.deleteRelation(
    1226                     getLayer(),
    1227                     toDelete
    1228             );
    1229         }
    1230 
    1231         @Override
    1232         public void actionPerformed(ActionEvent e) {
    1233             run();
    1234         }
    1235 
    1236         protected void updateEnabledState() {
    1237             setEnabled(getRelationSnapshot() != null);
    1238         }
    1239 
    1240         @Override
    1241         public void propertyChange(PropertyChangeEvent evt) {
    1242             if (evt.getPropertyName().equals(RELATION_SNAPSHOT_PROP)) {
    1243                 updateEnabledState();
    1244             }
    1245         }
    1246     }
    1247 
    1248     abstract class SavingAction extends AbstractAction {
    1249         /**
    1250          * apply updates to a new relation
    1251          */
    1252         protected void applyNewRelation() {
    1253             final Relation newRelation = new Relation();
    1254             tagEditorPanel.getModel().applyToPrimitive(newRelation);
    1255             memberTableModel.applyToRelation(newRelation);
    1256             List<RelationMember> newMembers = new ArrayList<>();
    1257             for (RelationMember rm: newRelation.getMembers()) {
    1258                 if (!rm.getMember().isDeleted()) {
    1259                     newMembers.add(rm);
    1260                 }
    1261             }
    1262             if (newRelation.getMembersCount() != newMembers.size()) {
    1263                 newRelation.setMembers(newMembers);
    1264                 String msg = tr("One or more members of this new relation have been deleted while the relation editor\n" +
    1265                 "was open. They have been removed from the relation members list.");
    1266                 JOptionPane.showMessageDialog(Main.parent, msg, tr("Warning"), JOptionPane.WARNING_MESSAGE);
    1267             }
    1268             // If the user wanted to create a new relation, but hasn't added any members or
    1269             // tags, don't add an empty relation
    1270             if (newRelation.getMembersCount() == 0 && !newRelation.hasKeys())
    1271                 return;
    1272             Main.main.undoRedo.add(new AddCommand(getLayer(), newRelation));
    1273 
    1274             // make sure everybody is notified about the changes
    1275             //
    1276             getLayer().data.fireSelectionChanged();
    1277             GenericRelationEditor.this.setRelation(newRelation);
    1278             RelationDialogManager.getRelationDialogManager().updateContext(
    1279                     getLayer(),
    1280                     getRelation(),
    1281                     GenericRelationEditor.this
    1282             );
    1283             SwingUtilities.invokeLater(new Runnable() {
    1284                 @Override
    1285                 public void run() {
    1286                     // Relation list gets update in EDT so selecting my be postponed to following EDT run
    1287                     Main.map.relationListDialog.selectRelation(newRelation);
    1288                 }
    1289             });
    1290         }
    1291 
    1292         /**
    1293          * Apply the updates for an existing relation which has been changed
    1294          * outside of the relation editor.
    1295          *
    1296          */
    1297         protected void applyExistingConflictingRelation() {
    1298             Relation editedRelation = new Relation(getRelation());
    1299             tagEditorPanel.getModel().applyToPrimitive(editedRelation);
    1300             memberTableModel.applyToRelation(editedRelation);
    1301             Conflict<Relation> conflict = new Conflict<>(getRelation(), editedRelation);
    1302             Main.main.undoRedo.add(new ConflictAddCommand(getLayer(), conflict));
    1303         }
    1304 
    1305         /**
    1306          * Apply the updates for an existing relation which has not been changed
    1307          * outside of the relation editor.
    1308          *
    1309          */
    1310         protected void applyExistingNonConflictingRelation() {
    1311             Relation editedRelation = new Relation(getRelation());
    1312             tagEditorPanel.getModel().applyToPrimitive(editedRelation);
    1313             memberTableModel.applyToRelation(editedRelation);
    1314             Main.main.undoRedo.add(new ChangeCommand(getRelation(), editedRelation));
    1315             getLayer().data.fireSelectionChanged();
    1316             // this will refresh the snapshot and update the dialog title
    1317             //
    1318             setRelation(getRelation());
    1319         }
    1320 
    1321         protected boolean confirmClosingBecauseOfDirtyState() {
    1322             ButtonSpec[] options = new ButtonSpec[] {
    1323                     new ButtonSpec(
    1324                             tr("Yes, create a conflict and close"),
    1325                             ImageProvider.get("ok"),
    1326                             tr("Click to create a conflict and close this relation editor"),
    1327                             null /* no specific help topic */
    1328                     ),
    1329                     new ButtonSpec(
    1330                             tr("No, continue editing"),
    1331                             ImageProvider.get("cancel"),
    1332                             tr("Click to return to the relation editor and to resume relation editing"),
    1333                             null /* no specific help topic */
    1334                     )
    1335             };
    1336 
    1337             int ret = HelpAwareOptionPane.showOptionDialog(
    1338                     Main.parent,
    1339                     tr("<html>This relation has been changed outside of the editor.<br>"
    1340                             + "You cannot apply your changes and continue editing.<br>"
    1341                             + "<br>"
    1342                             + "Do you want to create a conflict and close the editor?</html>"),
    1343                             tr("Conflict in data"),
    1344                             JOptionPane.WARNING_MESSAGE,
    1345                             null,
    1346                             options,
    1347                             options[0], // OK is default
    1348                             "/Dialog/RelationEditor#RelationChangedOutsideOfEditor"
    1349             );
    1350             return ret == 0;
    1351         }
    1352 
    1353         protected void warnDoubleConflict() {
    1354             JOptionPane.showMessageDialog(
    1355                     Main.parent,
    1356                     tr("<html>Layer ''{0}'' already has a conflict for object<br>"
    1357                             + "''{1}''.<br>"
    1358                             + "Please resolve this conflict first, then try again.</html>",
    1359                             getLayer().getName(),
    1360                             getRelation().getDisplayName(DefaultNameFormatter.getInstance())
    1361                     ),
    1362                     tr("Double conflict"),
    1363                     JOptionPane.WARNING_MESSAGE
    1364             );
    1365         }
    1366     }
    1367 
    1368     class ApplyAction extends SavingAction {
    1369         ApplyAction() {
    1370             putValue(SHORT_DESCRIPTION, tr("Apply the current updates"));
    1371             putValue(SMALL_ICON, ImageProvider.get("save"));
    1372             putValue(NAME, tr("Apply"));
    1373             setEnabled(true);
    1374         }
    1375 
    1376         public void run() {
    1377             if (getRelation() == null) {
    1378                 applyNewRelation();
    1379             } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot())
    1380                     || tagEditorPanel.getModel().isDirty()) {
    1381                 if (isDirtyRelation()) {
    1382                     if (confirmClosingBecauseOfDirtyState()) {
    1383                         if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
    1384                             warnDoubleConflict();
    1385                             return;
    1386                         }
    1387                         applyExistingConflictingRelation();
    1388                         setVisible(false);
    1389                     }
    1390                 } else {
    1391                     applyExistingNonConflictingRelation();
    1392                 }
    1393             }
    1394         }
    1395 
    1396         @Override
    1397         public void actionPerformed(ActionEvent e) {
    1398             run();
    1399         }
    1400     }
    1401 
    1402     class OKAction extends SavingAction {
    1403         OKAction() {
    1404             putValue(SHORT_DESCRIPTION, tr("Apply the updates and close the dialog"));
    1405             putValue(SMALL_ICON, ImageProvider.get("ok"));
    1406             putValue(NAME, tr("OK"));
    1407             setEnabled(true);
    1408         }
    1409 
    1410         public void run() {
    1411             Main.pref.put("relation.editor.generic.lastrole", tfRole.getText());
    1412             memberTable.stopHighlighting();
    1413             if (getRelation() == null) {
    1414                 applyNewRelation();
    1415             } else if (!memberTableModel.hasSameMembersAs(getRelationSnapshot())
    1416                     || tagEditorPanel.getModel().isDirty()) {
    1417                 if (isDirtyRelation()) {
    1418                     if (confirmClosingBecauseOfDirtyState()) {
    1419                         if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
    1420                             warnDoubleConflict();
    1421                             return;
    1422                         }
    1423                         applyExistingConflictingRelation();
    1424                     } else
    1425                         return;
    1426                 } else {
    1427                     applyExistingNonConflictingRelation();
    1428                 }
    1429             }
    1430             setVisible(false);
    1431         }
    1432 
    1433         @Override
    1434         public void actionPerformed(ActionEvent e) {
    1435             run();
    1436         }
    1437     }
    1438 
    1439     class CancelAction extends SavingAction {
    1440         CancelAction() {
    1441             putValue(SHORT_DESCRIPTION, tr("Cancel the updates and close the dialog"));
    1442             putValue(SMALL_ICON, ImageProvider.get("cancel"));
    1443             putValue(NAME, tr("Cancel"));
    1444 
    1445             getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
    1446             .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
    1447             getRootPane().getActionMap().put("ESCAPE", this);
    1448             setEnabled(true);
    1449         }
    1450 
    1451         @Override
    1452         public void actionPerformed(ActionEvent e) {
    1453             memberTable.stopHighlighting();
    1454             TagEditorModel tagModel = tagEditorPanel.getModel();
    1455             Relation snapshot = getRelationSnapshot();
    1456             if ((!memberTableModel.hasSameMembersAs(snapshot) || tagModel.isDirty())
    1457              && !(snapshot == null && tagModel.getTags().isEmpty())) {
    1458                 //give the user a chance to save the changes
    1459                 int ret = confirmClosingByCancel();
    1460                 if (ret == 0) { //Yes, save the changes
    1461                     //copied from OKAction.run()
    1462                     Main.pref.put("relation.editor.generic.lastrole", tfRole.getText());
    1463                     if (getRelation() == null) {
    1464                         applyNewRelation();
    1465                     } else if (!memberTableModel.hasSameMembersAs(snapshot) || tagModel.isDirty()) {
    1466                         if (isDirtyRelation()) {
    1467                             if (confirmClosingBecauseOfDirtyState()) {
    1468                                 if (getLayer().getConflicts().hasConflictForMy(getRelation())) {
    1469                                     warnDoubleConflict();
    1470                                     return;
    1471                                 }
    1472                                 applyExistingConflictingRelation();
    1473                             } else
    1474                                 return;
    1475                         } else {
    1476                             applyExistingNonConflictingRelation();
    1477                         }
    1478                     }
    1479                 } else if (ret == 2) //Cancel, continue editing
    1480                     return;
    1481                 //in case of "No, discard", there is no extra action to be performed here.
    1482             }
    1483             setVisible(false);
    1484         }
    1485 
    1486         protected int confirmClosingByCancel() {
    1487             ButtonSpec[] options = new ButtonSpec[] {
    1488                     new ButtonSpec(
    1489                             tr("Yes, save the changes and close"),
    1490                             ImageProvider.get("ok"),
    1491                             tr("Click to save the changes and close this relation editor"),
    1492                             null /* no specific help topic */
    1493                     ),
    1494                     new ButtonSpec(
    1495                             tr("No, discard the changes and close"),
    1496                             ImageProvider.get("cancel"),
    1497                             tr("Click to discard the changes and close this relation editor"),
    1498                             null /* no specific help topic */
    1499                     ),
    1500                     new ButtonSpec(
    1501                             tr("Cancel, continue editing"),
    1502                             ImageProvider.get("cancel"),
    1503                             tr("Click to return to the relation editor and to resume relation editing"),
    1504                             null /* no specific help topic */
    1505                     )
    1506             };
    1507 
    1508             return HelpAwareOptionPane.showOptionDialog(
    1509                     Main.parent,
    1510                     tr("<html>The relation has been changed.<br>"
    1511                             + "<br>"
    1512                             + "Do you want to save your changes?</html>"),
    1513                             tr("Unsaved changes"),
    1514                             JOptionPane.WARNING_MESSAGE,
    1515                             null,
    1516                             options,
    1517                             options[0], // OK is default,
    1518                             "/Dialog/RelationEditor#DiscardChanges"
    1519             );
    1520         }
    1521     }
    1522 
    1523     class AddTagAction extends AbstractAction {
    1524         AddTagAction() {
    1525             putValue(SHORT_DESCRIPTION, tr("Add an empty tag"));
    1526             putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
    1527             setEnabled(true);
    1528         }
    1529 
    1530         @Override
    1531         public void actionPerformed(ActionEvent e) {
    1532             tagEditorPanel.getModel().appendNewTag();
    1533         }
    1534     }
    1535 
    1536     class DownloadIncompleteMembersAction extends AbstractAction implements TableModelListener {
    1537         DownloadIncompleteMembersAction() {
    1538             String tooltip = tr("Download all incomplete members");
    1539             putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincomplete"));
    1540             putValue(NAME, tr("Download Members"));
    1541             Shortcut sc = Shortcut.registerShortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"),
    1542                 KeyEvent.VK_HOME, Shortcut.ALT);
    1543             sc.setAccelerator(this);
    1544             putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
    1545             updateEnabledState();
    1546         }
    1547 
    1548         @Override
    1549         public void actionPerformed(ActionEvent e) {
    1550             if (!isEnabled())
    1551                 return;
    1552             Main.worker.submit(new DownloadRelationMemberTask(
    1553                     getRelation(),
    1554                     memberTableModel.getIncompleteMemberPrimitives(),
    1555                     getLayer(),
    1556                     GenericRelationEditor.this)
    1557             );
    1558         }
    1559 
    1560         protected void updateEnabledState() {
    1561             setEnabled(memberTableModel.hasIncompleteMembers() && !Main.isOffline(OnlineResource.OSM_API));
    1562         }
    1563 
    1564         @Override
    1565         public void tableChanged(TableModelEvent e) {
    1566             updateEnabledState();
    1567         }
    1568     }
    1569 
    1570     class DownloadSelectedIncompleteMembersAction extends AbstractAction implements ListSelectionListener, TableModelListener {
    1571         DownloadSelectedIncompleteMembersAction() {
    1572             putValue(SHORT_DESCRIPTION, tr("Download selected incomplete members"));
    1573             putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected"));
    1574             putValue(NAME, tr("Download Members"));
    1575         //  Shortcut.register Shortcut("relationeditor:downloadincomplete", tr("Relation Editor: Download Members"), KeyEvent.VK_K, Shortcut.ALT)
    1576             updateEnabledState();
    1577         }
    1578 
    1579         @Override
    1580         public void actionPerformed(ActionEvent e) {
    1581             if (!isEnabled())
    1582                 return;
    1583             Main.worker.submit(new DownloadRelationMemberTask(
    1584                     getRelation(),
    1585                     memberTableModel.getSelectedIncompleteMemberPrimitives(),
    1586                     getLayer(),
    1587                     GenericRelationEditor.this)
    1588             );
    1589         }
    1590 
    1591         protected void updateEnabledState() {
    1592             setEnabled(memberTableModel.hasIncompleteSelectedMembers() && !Main.isOffline(OnlineResource.OSM_API));
    1593         }
    1594 
    1595         @Override
    1596         public void valueChanged(ListSelectionEvent e) {
    1597             updateEnabledState();
    1598         }
    1599 
    1600         @Override
    1601         public void tableChanged(TableModelEvent e) {
    1602             updateEnabledState();
    1603         }
    1604     }
    1605 
    1606     class SetRoleAction extends AbstractAction implements ListSelectionListener, DocumentListener {
    1607         SetRoleAction() {
    1608             putValue(SHORT_DESCRIPTION, tr("Sets a role for the selected members"));
    1609             putValue(SMALL_ICON, ImageProvider.get("apply"));
    1610             putValue(NAME, tr("Apply Role"));
    1611             refreshEnabled();
    1612         }
    1613 
    1614         protected void refreshEnabled() {
    1615             setEnabled(memberTable.getSelectedRowCount() > 0);
    1616         }
    1617 
    1618         protected boolean isEmptyRole() {
    1619             return tfRole.getText() == null || tfRole.getText().trim().isEmpty();
    1620         }
    1621 
    1622         protected boolean confirmSettingEmptyRole(int onNumMembers) {
    1623             String message = "<html>"
    1624                 + trn("You are setting an empty role on {0} object.",
    1625                         "You are setting an empty role on {0} objects.", onNumMembers, onNumMembers)
    1626                         + "<br>"
    1627                         + tr("This is equal to deleting the roles of these objects.") +
    1628                         "<br>"
    1629                         + tr("Do you really want to apply the new role?") + "</html>";
    1630             String[] options = new String[] {
    1631                     tr("Yes, apply it"),
    1632                     tr("No, do not apply")
    1633             };
    1634             int ret = ConditionalOptionPaneUtil.showOptionDialog(
    1635                     "relation_editor.confirm_applying_empty_role",
    1636                     Main.parent,
    1637                     message,
    1638                     tr("Confirm empty role"),
    1639                     JOptionPane.YES_NO_OPTION,
    1640                     JOptionPane.WARNING_MESSAGE,
    1641                     options,
    1642                     options[0]
    1643             );
    1644             switch(ret) {
    1645             case JOptionPane.YES_OPTION:
    1646             case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
    1647                 return true;
    1648             default:
    1649                 return false;
    1650             }
    1651         }
    1652 
    1653         @Override
    1654         public void actionPerformed(ActionEvent e) {
    1655             if (isEmptyRole()) {
    1656                 if (!confirmSettingEmptyRole(memberTable.getSelectedRowCount()))
    1657                     return;
    1658             }
    1659             memberTableModel.updateRole(memberTable.getSelectedRows(), tfRole.getText());
    1660         }
    1661 
    1662         @Override
    1663         public void valueChanged(ListSelectionEvent e) {
    1664             refreshEnabled();
    1665         }
    1666 
    1667         @Override
    1668         public void changedUpdate(DocumentEvent e) {
    1669             refreshEnabled();
    1670         }
    1671 
    1672         @Override
    1673         public void insertUpdate(DocumentEvent e) {
    1674             refreshEnabled();
    1675         }
    1676 
    1677         @Override
    1678         public void removeUpdate(DocumentEvent e) {
    1679             refreshEnabled();
    1680         }
    1681     }
    1682 
    1683     /**
    1684      * Creates a new relation with a copy of the current editor state.
    1685      */
    1686     class DuplicateRelationAction extends AbstractAction {
    1687         DuplicateRelationAction() {
    1688             putValue(SHORT_DESCRIPTION, tr("Create a copy of this relation and open it in another editor window"));
    1689             // FIXME provide an icon
    1690             putValue(SMALL_ICON, ImageProvider.get("duplicate"));
    1691             putValue(NAME, tr("Duplicate"));
    1692             setEnabled(true);
    1693         }
    1694 
    1695         @Override
    1696         public void actionPerformed(ActionEvent e) {
    1697             Relation copy = new Relation();
    1698             tagEditorPanel.getModel().applyToPrimitive(copy);
    1699             memberTableModel.applyToRelation(copy);
    1700             RelationEditor editor = RelationEditor.getEditor(getLayer(), copy, memberTableModel.getSelectedMembers());
    1701             editor.setVisible(true);
    1702         }
    1703     }
    1704 
    1705     /**
    1706      * Action for editing the currently selected relation.
    1707      */
    1708     class EditAction extends AbstractAction implements ListSelectionListener {
    1709         EditAction() {
    1710             putValue(SHORT_DESCRIPTION, tr("Edit the relation the currently selected relation member refers to"));
    1711             putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
    1712             refreshEnabled();
    1713         }
    1714 
    1715         protected void refreshEnabled() {
    1716             setEnabled(memberTable.getSelectedRowCount() == 1
    1717                     && memberTableModel.isEditableRelation(memberTable.getSelectedRow()));
    1718         }
    1719 
    1720         protected Collection<RelationMember> getMembersForCurrentSelection(Relation r) {
    1721             Collection<RelationMember> members = new HashSet<>();
    1722             Collection<OsmPrimitive> selection = getLayer().data.getSelected();
    1723             for (RelationMember member: r.getMembers()) {
    1724                 if (selection.contains(member.getMember())) {
    1725                     members.add(member);
    1726                 }
    1727             }
    1728             return members;
    1729         }
    1730 
    1731         public void run() {
    1732             int idx = memberTable.getSelectedRow();
    1733             if (idx < 0)
    1734                 return;
    1735             OsmPrimitive primitive = memberTableModel.getReferredPrimitive(idx);
    1736             if (!(primitive instanceof Relation))
    1737                 return;
    1738             Relation r = (Relation) primitive;
    1739             if (r.isIncomplete())
    1740                 return;
    1741 
    1742             RelationEditor editor = RelationEditor.getEditor(getLayer(), r, getMembersForCurrentSelection(r));
    1743             editor.setVisible(true);
    1744         }
    1745 
    1746         @Override
    1747         public void actionPerformed(ActionEvent e) {
    1748             if (!isEnabled())
    1749                 return;
    1750             run();
    1751         }
    1752 
    1753         @Override
    1754         public void valueChanged(ListSelectionEvent e) {
    1755             refreshEnabled();
    1756         }
    1757     }
    1758 
    1759     class PasteMembersAction extends AddFromSelectionAction {
    1760 
    1761         @Override
    1762         public void actionPerformed(ActionEvent e) {
    1763             try {
    1764                 List<PrimitiveData> primitives = Main.pasteBuffer.getDirectlyAdded();
    1765                 DataSet ds = getLayer().data;
    1766                 List<OsmPrimitive> toAdd = new ArrayList<>();
    1767                 boolean hasNewInOtherLayer = false;
    1768 
    1769                 for (PrimitiveData primitive: primitives) {
    1770                     OsmPrimitive primitiveInDs = ds.getPrimitiveById(primitive);
    1771                     if (primitiveInDs != null) {
    1772                         toAdd.add(primitiveInDs);
    1773                     } else if (!primitive.isNew()) {
    1774                         OsmPrimitive p = primitive.getType().newInstance(primitive.getUniqueId(), true);
    1775                         ds.addPrimitive(p);
    1776                         toAdd.add(p);
    1777                     } else {
    1778                         hasNewInOtherLayer = true;
    1779                         break;
    1780                     }
    1781                 }
    1782 
    1783                 if (hasNewInOtherLayer) {
    1784                     JOptionPane.showMessageDialog(Main.parent,
    1785                             tr("Members from paste buffer cannot be added because they are not included in current layer"));
    1786                     return;
    1787                 }
    1788 
    1789                 toAdd = filterConfirmedPrimitives(toAdd);
    1790                 int index = memberTableModel.getSelectionModel().getMaxSelectionIndex();
    1791                 if (index == -1) {
    1792                     index = memberTableModel.getRowCount() - 1;
    1793                 }
    1794                 memberTableModel.addMembersAfterIdx(toAdd, index);
    1795 
    1796                 tfRole.requestFocusInWindow();
    1797 
    1798             } catch (AddAbortException ex) {
    1799                 // Do nothing
    1800                 if (Main.isTraceEnabled()) {
    1801                     Main.trace(ex.getMessage());
    1802                 }
    1803             }
    1804         }
    1805     }
    1806 
    1807     class CopyMembersAction extends AbstractAction {
    1808         @Override
    1809         public void actionPerformed(ActionEvent e) {
    1810             Set<OsmPrimitive> primitives = new HashSet<>();
    1811             for (RelationMember rm: memberTableModel.getSelectedMembers()) {
    1812                 primitives.add(rm.getMember());
    1813             }
    1814             if (!primitives.isEmpty()) {
    1815                 CopyAction.copy(getLayer(), primitives);
    1816             }
    1817         }
    1818     }
    1819 
    1820829    class MemberTableDblClickAdapter extends MouseAdapter {
    1821830        @Override
    1822831        public void mouseClicked(MouseEvent e) {
    1823832            if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
    1824                 new EditAction().run();
     833                new EditAction(memberTable, memberTableModel, getLayer()).actionPerformed(null);
    1825834            }
    1826835        }
  • trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java

    r9388 r9496  
    703703     * Sort the selected relation members by the way they are linked.
    704704     */
    705     void sort() {
     705    public void sort() {
    706706        List<RelationMember> selectedMembers = new ArrayList<>(getSelectedMembers());
    707         List<RelationMember> sortedMembers = null;
     707        List<RelationMember> sortedMembers;
    708708        List<RelationMember> newMembers;
    709709        if (selectedMembers.size() <= 1) {
     
    727727        }
    728728
    729         if (members.size() != newMembers.size()) throw new AssertionError();
     729        if (members.size() != newMembers.size())
     730            throw new AssertionError();
    730731
    731732        members.clear();
     
    738739     * Sort the selected relation members and all members below by the way they are linked.
    739740     */
    740     void sortBelow() {
    741         final List<RelationMember> subList = members.subList(getSelectionModel().getMinSelectionIndex(), members.size());
     741    public void sortBelow() {
     742        final List<RelationMember> subList = members.subList(Math.max(0, getSelectionModel().getMinSelectionIndex()), members.size());
    742743        final List<RelationMember> sorted = relationSorter.sortMembers(subList);
    743744        subList.clear();
     
    766767     * Reverse the relation members.
    767768     */
    768     void reverse() {
     769    public void reverse() {
    769770        List<Integer> selectedIndices = getSelectedIndices();
    770771        List<Integer> selectedIndicesReversed = getSelectedIndices();
  • trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationEditor.java

    r9078 r9496  
    1919import org.openstreetmap.josm.tools.CheckParameterUtil;
    2020
    21 public abstract class RelationEditor extends ExtendedDialog {
     21/**
     22 * Abstract relation editor.
     23 * @since 1599
     24 */
     25public abstract class RelationEditor extends ExtendedDialog implements RelationAware {
     26
    2227    /** the property name for the current relation.
    2328     * @see #setRelation(Relation)
     
    3439    private static List<Class<RelationEditor>> editors = new ArrayList<>();
    3540
     41    /** The relation that this editor is working on. */
     42    private transient Relation relation;
     43
     44    /** The version of the relation when editing is started. This is null if a new relation is created. */
     45    private transient Relation relationSnapshot;
     46
     47    /** The data layer the relation belongs to */
     48    private final transient OsmDataLayer layer;
     49
     50    private final PropertyChangeSupport support = new PropertyChangeSupport(this);
     51
     52    /**
     53     * Creates a new relation editor
     54     *
     55     * @param layer the {@link OsmDataLayer} in whose context a relation is edited. Must not be null.
     56     * @param relation the relation. Can be null if a new relation is to be edited.
     57     * @throws IllegalArgumentException if layer is null
     58     */
     59    protected RelationEditor(OsmDataLayer layer, Relation relation) {
     60        super(Main.parent,
     61                "",
     62                new String[] {tr("Apply Changes"), tr("Cancel")},
     63                false,
     64                false
     65        );
     66        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
     67        this.layer = layer;
     68        setRelation(relation);
     69    }
     70
    3671    /**
    3772     * Registers a relation editor class. Depending on the type of relation to be edited
     
    4277     */
    4378    public void registerRelationEditor(Class<RelationEditor> clazz) {
    44         if (clazz == null) return;
    45         if (!editors.contains(clazz)) {
     79        if (clazz != null && !editors.contains(clazz)) {
    4680            editors.add(clazz);
    4781        }
     
    4983
    5084    /**
    51      * The relation that this editor is working on.
    52      */
    53     private transient Relation relation;
    54 
    55     /**
    56      * The version of the relation when editing is started.  This is
    57      * null if a new relation is created. */
    58     private transient Relation relationSnapshot;
    59 
    60     /** the data layer the relation belongs to */
    61     private final transient OsmDataLayer layer;
    62 
    63     /**
    64      * This is a factory method that creates an appropriate RelationEditor
    65      * instance suitable for editing the relation that was passed in as an
    66      * argument.
     85     * This is a factory method that creates an appropriate RelationEditor instance suitable for editing the relation
     86     * that was passed in as an argument.
    6787     *
    68      * This method is guaranteed to return a working RelationEditor. If no
    69      * specific editor has been registered for the type of relation, then
    70      * a generic editor will be returned.
     88     * This method is guaranteed to return a working RelationEditor. If no specific editor has been registered for the
     89     * type of relation, then a generic editor will be returned.
    7190     *
    72      * Editors can be registered by adding their class to the static list "editors"
    73      * in the RelationEditor class. When it comes to editing a relation, all
    74      * registered editors are queried via their static "canEdit" method whether they
    75      * feel responsible for that kind of relation, and if they return true
    76      * then an instance of that class will be used.
     91     * Editors can be registered by adding their class to the static list "editors" in the RelationEditor class.
     92     * When it comes to editing a relation, all registered editors are queried via their static "canEdit" method whether
     93     * they feel responsible for that kind of relation, and if they return true then an instance of that class will be used.
    7794     *
    7895     * @param layer the data layer the relation is a member of
    7996     * @param r the relation to be edited
    80      * @param selectedMembers a collection of relation members which shall be selected when the
    81      * editor is first launched
     97     * @param selectedMembers a collection of relation members which shall be selected when the editor is first launched
    8298     * @return an instance of RelationEditor suitable for editing that kind of relation
    8399     */
     
    106122
    107123    /**
    108      * Creates a new relation editor
    109      *
    110      * @param layer  the {@link OsmDataLayer} in whose context a relation is edited. Must not be null.
    111      * @param relation the relation. Can be null if a new relation is to be edited.
    112      * @param selectedMembers  a collection of members in <code>relation</code> which the editor
    113      * should display selected when the editor is first displayed on screen
    114      * @throws IllegalArgumentException if layer is null
    115      */
    116     protected RelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) {
    117         super(Main.parent,
    118                 "",
    119                 new String[] {tr("Apply Changes"), tr("Cancel")},
    120                 false,
    121                 false
    122         );
    123         CheckParameterUtil.ensureParameterNotNull(layer, "layer");
    124         this.layer = layer;
    125         setRelation(relation);
    126     }
    127 
    128     /**
    129124     * updates the title of the relation editor
    130125     */
     
    139134    }
    140135
    141     /**
    142      * Replies the currently edited relation
    143      *
    144      * @return the currently edited relation
    145      */
    146     protected Relation getRelation() {
     136    @Override
     137    public final Relation getRelation() {
    147138        return relation;
    148139    }
    149140
    150     /**
    151      * Sets the currently edited relation. Creates a snapshot of the current
    152      * state of the relation. See {@link #getRelationSnapshot()}
    153      *
    154      * @param relation the relation
    155      */
    156     protected void setRelation(Relation relation) {
     141    @Override
     142    public final void setRelation(Relation relation) {
    157143        setRelationSnapshot((relation == null) ? null : new Relation(relation));
    158144        Relation oldValue = this.relation;
     
    165151
    166152    /**
    167      * Replies the {@link OsmDataLayer} in whose context this relation editor is
    168      * open
     153     * Replies the {@link OsmDataLayer} in whose context this relation editor is open
    169154     *
    170      * @return the {@link OsmDataLayer} in whose context this relation editor is
    171      * open
     155     * @return the {@link OsmDataLayer} in whose context this relation editor is open
    172156     */
    173     protected OsmDataLayer getLayer() {
     157    protected final OsmDataLayer getLayer() {
    174158        return layer;
    175159    }
    176160
    177     /**
    178      * Replies the state of the edited relation when the editor has been launched
    179      *
    180      * @return the state of the edited relation when the editor has been launched
    181      */
    182     protected Relation getRelationSnapshot() {
     161    @Override
     162    public final Relation getRelationSnapshot() {
    183163        return relationSnapshot;
    184164    }
    185165
    186     protected void setRelationSnapshot(Relation snapshot) {
     166    protected final void setRelationSnapshot(Relation snapshot) {
    187167        Relation oldValue = relationSnapshot;
    188168        relationSnapshot = snapshot;
     
    192172    }
    193173
    194     /**
    195      * Replies true if the currently edited relation has been changed elsewhere.
    196      *
    197      * In this case a relation editor can't apply updates to the relation directly. Rather,
    198      * it has to create a conflict.
    199      *
    200      * @return true if the currently edited relation has been changed elsewhere.
    201      */
    202     protected boolean isDirtyRelation() {
     174    @Override
     175    public final boolean isDirtyRelation() {
    203176        return !relation.hasEqualSemanticAttributes(relationSnapshot);
    204177    }
     
    207180    /* property change support                                                 */
    208181    /* ----------------------------------------------------------------------- */
    209     private final PropertyChangeSupport support = new PropertyChangeSupport(this);
    210182
    211183    @Override
    212     public void addPropertyChangeListener(PropertyChangeListener listener) {
     184    public final void addPropertyChangeListener(PropertyChangeListener listener) {
    213185        this.support.addPropertyChangeListener(listener);
    214186    }
    215187
    216188    @Override
    217     public void removePropertyChangeListener(PropertyChangeListener listener) {
     189    public final void removePropertyChangeListener(PropertyChangeListener listener) {
    218190        this.support.removePropertyChangeListener(listener);
    219191    }
Note: See TracChangeset for help on using the changeset viewer.