Ticket #21227: 21227-2.patch

File 21227-2.patch, 47.9 KB (added by marcello@…, 5 years ago)

[PATCH] in relation editor filter autocompletion roles according to relation and member type

  • src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java

     
    2727import java.util.Collection;
    2828import java.util.Collections;
    2929import java.util.EnumSet;
     30import java.util.HashSet;
    3031import java.util.List;
    3132import java.util.Set;
    3233import java.util.stream.Collectors;
     
    4546import javax.swing.JSplitPane;
    4647import javax.swing.JTabbedPane;
    4748import javax.swing.JTable;
     49import javax.swing.JTextField;
    4850import javax.swing.JToolBar;
    4951import javax.swing.KeyStroke;
     52import javax.swing.event.PopupMenuEvent;
     53import javax.swing.event.PopupMenuListener;
    5054
    5155import org.openstreetmap.josm.actions.JosmAction;
    5256import org.openstreetmap.josm.command.ChangeMembersCommand;
     
    5862import org.openstreetmap.josm.data.osm.Relation;
    5963import org.openstreetmap.josm.data.osm.RelationMember;
    6064import org.openstreetmap.josm.data.osm.Tag;
     65import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
    6166import org.openstreetmap.josm.data.validation.tests.RelationChecker;
    6267import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    6368import org.openstreetmap.josm.gui.MainApplication;
     
    98103import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    99104import org.openstreetmap.josm.gui.tagging.TagEditorModel;
    100105import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
    101 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    102 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
     106import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
     107import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
     108import org.openstreetmap.josm.gui.tagging.ac.AutoCompEvent;
     109import org.openstreetmap.josm.gui.tagging.ac.AutoCompListener;
     110import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
    103111import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
    104112import 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.tagging.presets.items.Roles.Role;
    108117import org.openstreetmap.josm.gui.util.WindowGeometry;
    109118import org.openstreetmap.josm.spi.preferences.Config;
    110119import org.openstreetmap.josm.tools.CheckParameterUtil;
     
    117126 * This dialog is for editing relations.
    118127 * @since 343
    119128 */
    120 public class GenericRelationEditor extends RelationEditor implements CommandQueueListener {
     129public class GenericRelationEditor extends RelationEditor implements AutoCompListener, CommandQueueListener, PopupMenuListener {
    121130    /** the tag table and its model */
    122131    private final TagEditorPanel tagEditorPanel;
    123132    private final ReferringRelationsBrowser referrerBrowser;
     
    131140    private final SelectionTable selectionTable;
    132141    private final SelectionTableModel selectionTableModel;
    133142
    134     private final AutoCompletingTextField tfRole;
     143    private final AutoCompletionManager manager;
     144    private final AutoCompComboBox<AutoCompletionItem> cbRole;
    135145
    136146    /**
    137147     * the menu item in the windows menu. Required to properly hide on dialog close.
     
    216226        populateModels(relation);
    217227        tagEditorPanel.getModel().ensureOneTag();
    218228
     229        manager = AutoCompletionManager.of(this.getLayer().data);
     230
    219231        // setting up the member table
    220         memberTable = new MemberTable(getLayer(), getRelation(), memberTableModel);
     232        memberTable = new MemberTable(getLayer(), this, memberTableModel);
    221233        memberTable.addMouseListener(new MemberTableDblClickAdapter());
    222234        memberTableModel.addMemberModelListener(memberTable);
    223235
     
    226238        selectionTable.setRowHeight(ce.getEditor().getPreferredSize().height);
    227239
    228240        LeftButtonToolbar leftButtonToolbar = new LeftButtonToolbar(new RelationEditorActionAccess());
    229         tfRole = buildRoleTextField(this);
     241        cbRole = buildRoleComboBox();
     242        cbRole.getEditorComponent().addAutoCompListener(this);
     243        cbRole.addPopupMenuListener(this);
    230244
    231245        JSplitPane pane = buildSplitPane(
    232246                buildTagEditorPanel(tagEditorPanel),
    233                 buildMemberEditorPanel(leftButtonToolbar, new RelationEditorActionAccess()),
     247                buildMemberEditorPanel(leftButtonToolbar),
    234248                this);
    235249        pane.setPreferredSize(new Dimension(100, 100));
    236250
     
    310324                @Override
    311325                public void actionPerformed(ActionEvent e) {
    312326                    super.actionPerformed(e);
    313                     tfRole.requestFocusInWindow();
     327                    cbRole.requestFocusInWindow();
    314328                }
    315329            }, "PASTE_MEMBERS", key, getRootPane(), memberTable, selectionTable);
    316330        }
     
    446460    }
    447461
    448462    /**
    449      * builds the role text field
    450      * @param re relation editor
     463     * builds a role text field
    451464     * @return the role text field
    452465     */
    453     protected static AutoCompletingTextField buildRoleTextField(final IRelationEditor re) {
    454         final AutoCompletingTextField tfRole = new AutoCompletingTextField(10);
    455         tfRole.setToolTipText(tr("Enter a role and apply it to the selected relation members"));
    456         tfRole.addFocusListener(new FocusAdapter() {
     466    protected static AutoCompComboBox<AutoCompletionItem> buildRoleComboBox() {
     467        final AutoCompComboBox<AutoCompletionItem> cbRole = new AutoCompComboBox<>();
     468        cbRole.getEditorComponent().setColumns(10);
     469        cbRole.setToolTipText(tr("Enter a role and apply it to the selected relation members"));
     470        cbRole.addFocusListener(new FocusAdapter() {
    457471            @Override
    458472            public void focusGained(FocusEvent e) {
    459                 tfRole.selectAll();
     473                cbRole.getEditorComponent().selectAll();
    460474            }
    461475        });
    462         tfRole.setAutoCompletionList(new AutoCompletionList());
    463         tfRole.addFocusListener(
    464                 new FocusAdapter() {
    465                     @Override
    466                     public void focusGained(FocusEvent e) {
    467                         AutoCompletionList list = tfRole.getAutoCompletionList();
    468                         if (list != null) {
    469                             list.clear();
    470                             AutoCompletionManager.of(re.getLayer().data).populateWithMemberRoles(list, re.getRelation());
    471                         }
    472                     }
    473                 }
    474         );
    475         tfRole.setText(Config.getPref().get("relation.editor.generic.lastrole", ""));
    476         return tfRole;
     476        cbRole.setText(Config.getPref().get("relation.editor.generic.lastrole", ""));
     477        return cbRole;
    477478    }
    478479
    479480    /**
    480481     * builds the panel for the relation member editor
    481482     * @param leftButtonToolbar left button toolbar
    482      * @param editorAccess The relation editor
    483483     *
    484484     * @return the panel for the relation member editor
    485485     */
    486     protected static JPanel buildMemberEditorPanel(
    487             LeftButtonToolbar leftButtonToolbar, IRelationEditorActionAccess editorAccess) {
     486    protected JPanel buildMemberEditorPanel(LeftButtonToolbar leftButtonToolbar) {
    488487        final JPanel pnl = new JPanel(new GridBagLayout());
    489         final JScrollPane scrollPane = new JScrollPane(editorAccess.getMemberTable());
     488        final JScrollPane scrollPane = new JScrollPane(memberTable);
    490489
    491490        GridBagConstraints gc = new GridBagConstraints();
    492491        gc.gridx = 0;
     
    520519        // --- role editing
    521520        JPanel p3 = new JPanel(new FlowLayout(FlowLayout.LEFT));
    522521        p3.add(new JLabel(tr("Apply Role:")));
    523         p3.add(editorAccess.getTextFieldRole());
    524         SetRoleAction setRoleAction = new SetRoleAction(editorAccess);
    525         editorAccess.getMemberTableModel().getSelectionModel().addListSelectionListener(setRoleAction);
    526         editorAccess.getTextFieldRole().getDocument().addDocumentListener(setRoleAction);
    527         editorAccess.getTextFieldRole().addActionListener(setRoleAction);
    528         editorAccess.getMemberTableModel().getSelectionModel().addListSelectionListener(
    529                 e -> editorAccess.getTextFieldRole().setEnabled(editorAccess.getMemberTable().getSelectedRowCount() > 0)
     522        p3.add(cbRole);
     523        SetRoleAction setRoleAction = new SetRoleAction(new RelationEditorActionAccess());
     524        memberTableModel.getSelectionModel().addListSelectionListener(setRoleAction);
     525        cbRole.getEditorComponent().getDocument().addDocumentListener(setRoleAction);
     526        cbRole.getEditorComponent().addActionListener(setRoleAction);
     527        memberTableModel.getSelectionModel().addListSelectionListener(
     528                e -> cbRole.setEnabled(memberTable.getSelectedRowCount() > 0)
    530529        );
    531         editorAccess.getTextFieldRole().setEnabled(editorAccess.getMemberTable().getSelectedRowCount() > 0);
     530        cbRole.setEnabled(memberTable.getSelectedRowCount() > 0);
     531
    532532        JButton btnApply = new JButton(setRoleAction);
    533         btnApply.setPreferredSize(new Dimension(20, 20));
     533        int height = cbRole.getPreferredSize().height;
     534        btnApply.setPreferredSize(new Dimension(height, height));
    534535        btnApply.setText("");
    535536        p3.add(btnApply);
    536537
     
    562563        gc.anchor = GridBagConstraints.NORTHWEST;
    563564        gc.weightx = 0.0;
    564565        gc.weighty = 1.0;
    565         pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar(editorAccess),
     566        pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar(new RelationEditorActionAccess()),
    566567                ScrollViewport.VERTICAL_DIRECTION), gc);
    567568
    568569        gc.gridx = 1;
     
    570571        gc.weightx = 1.0;
    571572        gc.weighty = 1.0;
    572573        gc.fill = GridBagConstraints.BOTH;
    573         pnl2.add(buildSelectionTablePanel(editorAccess.getSelectionTable()), gc);
     574        pnl2.add(buildSelectionTablePanel(selectionTable), gc);
    574575
    575576        final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
    576577        splitPane.setLeftComponent(pnl);
    577578        splitPane.setRightComponent(pnl2);
    578579        splitPane.setOneTouchExpandable(false);
    579         if (editorAccess.getEditor() instanceof Window) {
    580             ((Window) editorAccess.getEditor()).addWindowListener(new WindowAdapter() {
    581                 @Override
    582                 public void windowOpened(WindowEvent e) {
    583                     // has to be called when the window is visible, otherwise no effect
    584                     splitPane.setDividerLocation(0.6);
    585                 }
    586             });
    587         }
     580        addWindowListener(new WindowAdapter() {
     581            @Override
     582            public void windowOpened(WindowEvent e) {
     583                // has to be called when the window is visible, otherwise no effect
     584                splitPane.setDividerLocation(0.6);
     585            }
     586        });
    588587
    589588        JPanel pnl3 = new JPanel(new BorderLayout());
    590589        pnl3.add(splitPane, BorderLayout.CENTER);
     
    10391038        }
    10401039
    10411040        @Override
    1042         public AutoCompletingTextField getTextFieldRole() {
    1043             return tfRole;
     1041        public JTextField getTextFieldRole() {
     1042            return cbRole.getEditorComponent();
    10441043        }
    1045 
    10461044    }
    10471045
    10481046    @Override
     
    10541052            applyAction.updateEnabledState();
    10551053        }
    10561054    }
     1055
     1056    private void initAutoCompModel(AutoCompComboBoxModel<AutoCompletionItem> model) {
     1057        Set<Role> currentRoles = new HashSet<>();
     1058        EnumSet<TaggingPresetType> selectedTypes = EnumSet.noneOf(TaggingPresetType.class);
     1059        for (int i = 0; i < memberTableModel.getRowCount(); ++i) {
     1060            RelationMember member = memberTableModel.getValue(i);
     1061            Role role = new Role();
     1062            role.key = member.getRole();
     1063            role.types = EnumSet.of(TaggingPresetType.forPrimitiveType(member.getDisplayType()));
     1064            currentRoles.add(role);
     1065        }
     1066        for (RelationMember member : memberTableModel.getSelectedMembers()) {
     1067            selectedTypes.add(TaggingPresetType.forPrimitiveType(member.getDisplayType()));
     1068        }
     1069        model.removeAllElements();
     1070        model.addAllElements(manager.getRolesForRelation(tagEditorPanel.getModel().getTags(), currentRoles, selectedTypes));
     1071    }
     1072
     1073    @Override
     1074    @SuppressWarnings("unchecked")
     1075    public void autoCompBefore(AutoCompEvent e) {
     1076        AutoCompTextField<AutoCompletionItem> tf = (AutoCompTextField<AutoCompletionItem>) e.getSource();
     1077        String savedText = tf.getText();
     1078        initAutoCompModel(tf.getModel());
     1079        tf.setText(savedText);
     1080    }
     1081
     1082    @Override
     1083    public void autoCompPerformed(AutoCompEvent e) {
     1084        // Not interested
     1085    }
     1086
     1087    @Override
     1088    @SuppressWarnings("unchecked")
     1089    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
     1090        AutoCompComboBox<AutoCompletionItem> cb = (AutoCompComboBox<AutoCompletionItem>) e.getSource();
     1091        String savedText = cb.getText();
     1092        initAutoCompModel(((AutoCompComboBox<AutoCompletionItem>) e.getSource()).getModel());
     1093        cb.setText(savedText);
     1094    }
     1095
     1096    @Override
     1097    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
     1098        // Who cares?
     1099    }
     1100
     1101    @Override
     1102    public void popupMenuCanceled(PopupMenuEvent e) {
     1103        // Who cares?
     1104    }
    10571105}
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java

     
    99import javax.swing.JTable;
    1010import javax.swing.table.TableCellEditor;
    1111
    12 import org.openstreetmap.josm.data.osm.Relation;
    13 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    14 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
    15 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
     12import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
     13import org.openstreetmap.josm.gui.tagging.ac.AutoCompListener;
     14import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
    1615
    1716/**
    1817 * The {@link CellEditor} for the role cell in the table. Supports autocompletion.
    1918 */
    2019public class MemberRoleCellEditor extends AbstractCellEditor implements TableCellEditor {
    21     private final AutoCompletingTextField editor;
    22     private final AutoCompletionManager autoCompletionManager;
    23     private final transient Relation relation;
     20    private final AutoCompTextField<AutoCompletionItem> editor;
    2421
    25     /** user input is matched against this list of auto completion items */
    26     private final AutoCompletionList autoCompletionList;
    27 
    2822    /**
    2923     * Constructs a new {@code MemberRoleCellEditor}.
    30      * @param autoCompletionManager the auto completion manager. Must not be null
    31      * @param relation the relation. Can be null
     24     * @param listener the autocompletion listener
    3225     * @since 13675
    3326     */
    34     public MemberRoleCellEditor(AutoCompletionManager autoCompletionManager, Relation relation) {
    35         this.autoCompletionManager = autoCompletionManager;
    36         this.relation = relation;
    37         editor = new AutoCompletingTextField(0, false);
     27    public MemberRoleCellEditor(AutoCompListener listener) {
     28        editor = new AutoCompTextField<>(0, false);
    3829        editor.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
    39         autoCompletionList = new AutoCompletionList();
    40         editor.setAutoCompletionList(autoCompletionList);
     30        editor.addAutoCompListener(listener);
    4131    }
    4232
    4333    @Override
    4434    public Component getTableCellEditorComponent(JTable table,
    4535            Object value, boolean isSelected, int row, int column) {
    46 
    4736        String role = (String) value;
    4837        editor.setText(role);
    49         autoCompletionList.clear();
    50         autoCompletionManager.populateWithMemberRoles(autoCompletionList, relation);
    5138        return editor;
    5239    }
    5340
     
    6047     * Returns the edit field for this cell editor.
    6148     * @return the edit field for this cell editor
    6249     */
    63     public AutoCompletingTextField getEditor() {
     50    public AutoCompTextField<AutoCompletionItem> getEditor() {
    6451        return editor;
    6552    }
    6653}
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java

     
    2727import org.openstreetmap.josm.actions.HistoryInfoAction;
    2828import org.openstreetmap.josm.actions.ZoomToAction;
    2929import org.openstreetmap.josm.data.osm.OsmPrimitive;
    30 import org.openstreetmap.josm.data.osm.Relation;
    3130import org.openstreetmap.josm.data.osm.RelationMember;
    3231import org.openstreetmap.josm.data.osm.Way;
    3332import org.openstreetmap.josm.gui.MainApplication;
     
    4140import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
    4241import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
    4342import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    44 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
     43import org.openstreetmap.josm.gui.tagging.ac.AutoCompListener;
    4544import org.openstreetmap.josm.gui.util.HighlightHelper;
    4645import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable;
    4746import org.openstreetmap.josm.spi.preferences.Config;
     
    6059     * constructor for relation member table
    6160     *
    6261     * @param layer the data layer of the relation. Must not be null
    63      * @param relation the relation. Can be null
     62     * @param listener the autocompletion listener
    6463     * @param model the table model
    6564     */
    66     public MemberTable(OsmDataLayer layer, Relation relation, MemberTableModel model) {
    67         super(model, new MemberTableColumnModel(AutoCompletionManager.of(layer.data), relation), model.getSelectionModel());
     65    public MemberTable(OsmDataLayer layer, AutoCompListener listener, MemberTableModel model) {
     66        super(model, new MemberTableColumnModel(listener), model.getSelectionModel());
    6867        setLayer(layer);
    6968        model.addMemberModelListener(this);
    7069
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableColumnModel.java

     
    66import javax.swing.table.DefaultTableColumnModel;
    77import javax.swing.table.TableColumn;
    88
    9 import org.openstreetmap.josm.data.osm.Relation;
    10 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
     9import org.openstreetmap.josm.gui.tagging.ac.AutoCompListener;
    1110
    1211/**
    1312 * This is the column model for the {@link MemberTable}
     
    1615
    1716    /**
    1817     * Constructs a new {@code MemberTableColumnModel}.
    19      * @param autoCompletionManager the auto completion manager. Must not be null
    20      * @param relation the relation. Can be null
     18     * @param listener the autocompletion listener
    2119     * @since 13675
    2220     */
    23     public MemberTableColumnModel(AutoCompletionManager autoCompletionManager, Relation relation) {
     21    public MemberTableColumnModel(AutoCompListener listener) {
    2422        TableColumn col;
    2523
    2624        // column 0 - the member role
     
    2927        col.setResizable(true);
    3028        col.setPreferredWidth(100);
    3129        col.setCellRenderer(new MemberTableRoleCellRenderer());
    32         col.setCellEditor(new MemberRoleCellEditor(autoCompletionManager, relation));
     30        col.setCellEditor(new MemberRoleCellEditor(listener));
    3331        addColumn(col);
    3432
    3533        // column 1 - the member
  • src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java

     
    22package org.openstreetmap.josm.gui.dialogs.relation.actions;
    33
    44import javax.swing.Action;
     5import javax.swing.JTextField;
    56
    67import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor;
    78import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
     
    910import org.openstreetmap.josm.gui.dialogs.relation.SelectionTable;
    1011import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
    1112import org.openstreetmap.josm.gui.tagging.TagEditorModel;
    12 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    1313
    1414/**
    1515 * This interface provides access to the relation editor for actions.
     
    6969     * Get the text field that is used to edit the role.
    7070     * @return The role text field.
    7171     */
    72     AutoCompletingTextField getTextFieldRole();
     72    JTextField getTextFieldRole();
    7373
    7474    /**
    7575     * Tells the member table editor to stop editing and accept any partially edited value as the value of the editor.
  • src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java

     
    88import java.util.List;
    99
    1010import javax.swing.JOptionPane;
     11import javax.swing.JTextField;
    1112import javax.swing.SwingUtilities;
    1213
    1314import org.openstreetmap.josm.command.AddCommand;
     
    2728import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager;
    2829import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
    2930import org.openstreetmap.josm.gui.tagging.TagEditorModel;
    30 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    3131import org.openstreetmap.josm.tools.ImageProvider;
    3232import org.openstreetmap.josm.tools.Utils;
    3333
     
    3838abstract class SavingAction extends AbstractRelationEditorAction {
    3939    private static final long serialVersionUID = 1L;
    4040
    41     protected final AutoCompletingTextField tfRole;
     41    protected final JTextField tfRole;
    4242
    4343    protected SavingAction(IRelationEditorActionAccess editorAccess, IRelationEditorUpdateOn... updateOn) {
    4444        super(editorAccess, updateOn);
  • src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java

     
    77import java.awt.event.ActionEvent;
    88
    99import javax.swing.JOptionPane;
     10import javax.swing.JTextField;
    1011import javax.swing.event.DocumentEvent;
    1112import javax.swing.event.DocumentListener;
    1213
    1314import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    1415import org.openstreetmap.josm.gui.MainApplication;
    15 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    1616import org.openstreetmap.josm.tools.ImageProvider;
    1717import org.openstreetmap.josm.tools.Utils;
    1818
     
    2323public class SetRoleAction extends AbstractRelationEditorAction implements DocumentListener {
    2424    private static final long serialVersionUID = 1L;
    2525
    26     private final transient AutoCompletingTextField tfRole;
     26    private final transient JTextField tfRole;
    2727
    2828    /**
    2929     * Constructs a new {@code SetRoleAction}.
  • src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java

     
    104104     * @param elems The string items to set
    105105     * @deprecated Has been moved to the model, where it belongs. Use
    106106     *     {@link org.openstreetmap.josm.gui.widgets.HistoryComboBoxModel#addAllStrings} instead. Probably you want to use
    107      *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#load} and
    108      *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#save}.
     107     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#load} and
     108     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#save}.
    109109     */
    110110    @Deprecated
    111111    public void setPossibleItems(Collection<E> elems) {
     
    122122     * @since 15011
    123123     * @deprecated Has been moved to the model, where it belongs. Use
    124124     *     {@link org.openstreetmap.josm.gui.widgets.HistoryComboBoxModel#addAllStrings} instead. Probably you want to use
    125      *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#load} and
    126      *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#save}.
     125     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#load} and
     126     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#save}.
    127127     */
    128128    @Deprecated
    129129    public void setPossibleItemsTopDown(Collection<E> elems) {
  • src/org/openstreetmap/josm/gui/tagging/ac/AutoCompListener.java

     
    44import java.util.EventListener;
    55
    66/**
    7  * The listener interface for receiving autoComp events.
    8  * The class that is interested in processing an autoComp event
    9  * implements this interface, and the object created with that
    10  * class is registered with a component, using the component's
    11  * <code>addAutoCompListener</code> method. When the autoComp event
    12  * occurs, that object's <code>autoCompPerformed</code> method is
    13  * invoked.
     7 * The listener interface for receiving AutoCompEvent events.
     8 * <p>
     9 * The class that is interested in processing an {@link AutoCompEvent} implements this interface,
     10 * and the object created with that class is registered with an autocompleting component using the
     11 * autocompleting component's {@link AutoCompTextField#addAutoCompListener addAutoCompListener}
     12 * method.
     13 * <p>
     14 * Before the autocompletion searches for candidates, the listener's {@code autoCompBefore} method
     15 * is invoked. It can be used to initialize the {@link AutoCompComboBoxModel}. After the
     16 * autocompletion occured the listener's {@code autoCompPerformed} method is invoked. It is used eg.
     17 * for adjusting the selection of an {@link AutoCompComboBox} after its {@link AutoCompTextField}
     18 * has autocompleted.
    1419 *
    15  * @see AutoCompEvent
    1620 * @since 18221
    1721 */
    1822public interface AutoCompListener extends EventListener {
  • src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java

     
    66import java.util.Collection;
    77import java.util.Collections;
    88import java.util.Comparator;
     9import java.util.EnumSet;
    910import java.util.HashMap;
    10 import java.util.HashSet;
    1111import java.util.LinkedHashSet;
    1212import java.util.List;
    1313import java.util.Map;
     
    4040import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
    4141import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    4242import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
     43import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
    4344import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    4445import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
    4546import org.openstreetmap.josm.tools.CheckParameterUtil;
     
    4748import org.openstreetmap.josm.tools.Utils;
    4849
    4950/**
    50  * AutoCompletionManager holds a cache of keys with a list of
    51  * possible auto completion values for each key.
    52  *
     51 * AutoCompletionManager holds a cache of keys with a list of possible auto completion values for
     52 * each key.
     53 * <p>
    5354 * Each DataSet can be assigned one AutoCompletionManager instance such that
    5455 * <ol>
    5556 *   <li>any key used in a tag in the data set is part of the key list in the cache</li>
     
    5657 *   <li>any value used in a tag for a specific key is part of the autocompletion list of this key</li>
    5758 * </ol>
    5859 *
    59  * Building up auto completion lists should not
    60  * slow down tabbing from input field to input field. Looping through the complete
    61  * data set in order to build up the auto completion list for a specific input
    62  * field is not efficient enough, hence this cache.
    63  *
    64  * TODO: respect the relation type for member role autocompletion
     60 * Building up auto completion lists should not slow down tabbing from input field to input field.
     61 * Looping through the complete data set in order to build up the auto completion list for a
     62 * specific input field is not efficient enough, hence this cache.
    6563 */
    6664public class AutoCompletionManager implements DataSetListener {
    6765
     
    105103        }
    106104    }
    107105
     106    static final Comparator<AutoCompletionItem> ALPHABETIC_AUTOCOMPLETIONITEM_COMPARATOR =
     107        (o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getValue(), o2.getValue());
     108
     109
    108110    /** If the dirty flag is set true, a rebuild is necessary. */
    109111    protected boolean dirty;
    110112    /** The data set that is managed */
     
    128130    static final Set<UserInputTag> USER_INPUT_TAG_CACHE = new LinkedHashSet<>();
    129131
    130132    /**
    131      * the cached list of member roles
    132      * only accessed by getRoleCache(), rebuild() and cacheRelationMemberRoles()
    133      * use getRoleCache() accessor
     133     * The cached list of member roles by relation {@code type}.
     134     * <p>
     135     * Used by rebuild() and cacheRelationMemberRoles().  Use the {@link #getRoleCache} accessor.
    134136     */
    135     protected Set<String> roleCache;
     137    protected final MultiMap<String, Role> ROLE_CACHE = new MultiMap<>();
    136138
    137     /**
    138      * the same as roleCache but for the preset roles can be accessed directly
    139      */
    140     static final Set<String> PRESET_ROLE_CACHE = new HashSet<>();
    141 
    142139    private static final Map<DataSet, AutoCompletionManager> INSTANCES = new HashMap<>();
    143140
    144141    /**
     
    159156        return tagCache;
    160157    }
    161158
    162     protected Set<String> getRoleCache() {
     159    protected MultiMap<String, Role> getRoleCache() {
    163160        if (dirty) {
    164161            rebuild();
    165162            dirty = false;
    166163        }
    167         return roleCache;
     164        return ROLE_CACHE;
    168165    }
    169166
    170167    /**
     
    172169     */
    173170    protected void rebuild() {
    174171        tagCache = new MultiMap<>();
    175         roleCache = new HashSet<>();
     172        ROLE_CACHE.clear();
    176173        cachePrimitives(ds.allNonDeletedCompletePrimitives());
    177174    }
    178175
     
    196193    }
    197194
    198195    /**
     196     * Returns the relation type.
     197     * <p>
     198     * This is used to categorize the relations in the dataset.  A relation with the keys:
     199     * <ul>
     200     * <li>type=route
     201     * <li>route=hiking
     202     * </ul>
     203     * will return a relation type of {@code "route.hiking"}.
     204     *
     205     * @param tags the tags on the relation
     206     * @return the relation type
     207     */
     208    private String getRelationType(Map<String, String> tags) {
     209        String type = tags.get("type");
     210        if (type == null) return "";
     211        String subtype = tags.get(type);
     212        if (subtype == null) return type;
     213        return type + "." + subtype;
     214    }
     215
     216    /**
    199217     * Caches all member roles of the relation <code>relation</code>
    200218     *
    201219     * @param relation the relation
    202220     */
    203221    protected void cacheRelationMemberRoles(Relation relation) {
    204         for (RelationMember m: relation.getMembers()) {
    205             if (m.hasRole()) {
    206                 roleCache.add(m.getRole());
     222        String type = getRelationType(relation.getKeys());
     223        for (RelationMember member: relation.getMembers()) {
     224            if (type != null && member.hasRole()) {
     225                Role role = new Role();
     226                role.key = member.getRole();
     227                role.types = EnumSet.of(TaggingPresetType.forPrimitiveType(member.getDisplayType()));
     228                ROLE_CACHE.put(type, role);
    207229            }
    208230        }
    209231    }
     
    263285     *
    264286     * @return the list of member roles
    265287     */
    266     public List<String> getMemberRoles() {
    267         return new ArrayList<>(getRoleCache());
     288    public List<Role> getMemberRoles() {
     289        return new ArrayList<>(getRoleCache().getAllValues());
    268290    }
    269291
    270292    /**
     
    273295     * @param list the list to populate
    274296     */
    275297    public void populateWithMemberRoles(AutoCompletionList list) {
    276         list.add(TaggingPresets.getPresetRoles(), AutoCompletionPriority.IS_IN_STANDARD);
    277         list.add(getRoleCache(), AutoCompletionPriority.IS_IN_DATASET);
     298        list.add(TaggingPresets.getPresetRoles().stream().map(r -> r.key).collect(Collectors.toList()), AutoCompletionPriority.IS_IN_STANDARD);
     299        list.add(getRoleCache().getAllValues().stream().map(r -> r.key).collect(Collectors.toList()), AutoCompletionPriority.IS_IN_DATASET);
    278300    }
    279301
    280302    /**
     
    303325    }
    304326
    305327    /**
     328     * Return true if the role may be applied to all of the types.
     329     * <p>
     330     * Returns true if {@code role.types} contains all elements of {@code types}.
     331     *
     332     * @param role The role.
     333     * @param types The types.
     334     * @return True if the role may be applied to all of the types.
     335     */
     336    private boolean appliesTo(Role role, EnumSet<TaggingPresetType> types) {
     337        return role.types.containsAll(types);
     338    }
     339
     340    /**
     341     * Returns all suitable roles for a given relation member.
     342     * <p>
     343     * This function implements the relation role filter. It gets the suitable roles from
     344     * <ul>
     345     * <li>matching tagging presets and from
     346     * <li>relations of the same type in the dataset.
     347     * </ul>
     348     * If no matching preset is found, returns all roles of all presets.
     349     *
     350     * @param keys current keys in the tag editor panel, used to determine the relation type
     351     * @param roles all roles of all members in the member editor panel
     352     * @param types the union of the types of the selected roles in the member editor panel
     353     * @return list of {@link AutoCompletionItem}s, alphabetically sorted
     354     * @since xxx
     355     */
     356    public List<AutoCompletionItem> getRolesForRelation(Map<String, String> keys, Set<Role> roles, EnumSet<TaggingPresetType> types) {
     357        Map<String, AutoCompletionPriority> map = new HashMap<>();
     358
     359        // the empty role
     360        map.merge("", AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
     361        // the roles already present in the member editor
     362        for (Role role : roles) {
     363            if (appliesTo(role, types))
     364                map.merge(role.key, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith);
     365        }
     366        // the roles on existing relations of the same type
     367        String type = getRelationType(keys);
     368        if (type != null) {
     369            for (Role role : getRoleCache().getValues(type)) {
     370                if (appliesTo(role, types))
     371                    map.merge(role.key, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith);
     372            }
     373        }
     374        Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets(null, keys, false);
     375        if (presets.isEmpty()) {
     376            // no preset matched, all roles of all presets
     377            for (Role role : TaggingPresets.getPresetRoles()) {
     378                if (appliesTo(role, types))
     379                    map.merge(role.key, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
     380            }
     381            for (Role role : getRoleCache().getAllValues()) {
     382                if (appliesTo(role, types))
     383                    map.merge(role.key, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith);
     384            }
     385        } else {
     386            // all roles of all matched presets
     387            for (TaggingPreset preset : presets) {
     388                if (preset.roles != null) {
     389                    for (Role role : preset.roles.roles) {
     390                        if (appliesTo(role, types))
     391                            map.merge(role.key, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
     392                    }
     393                }
     394            }
     395        }
     396        return map.entrySet().stream().map(e -> new AutoCompletionItem(e.getKey(), e.getValue()))
     397            .sorted(ALPHABETIC_AUTOCOMPLETIONITEM_COMPARATOR).collect(Collectors.toList());
     398    }
     399
     400    /**
    306401     * Populates the an {@link AutoCompletionList} with the currently cached tag keys
    307402     *
    308403     * @param list the list to populate
     
    503598                    MainApplication.getLayerManager().removeLayerChangeListener(this);
    504599                    dirty = true;
    505600                    tagCache = null;
    506                     roleCache = null;
     601                    ROLE_CACHE.clear();
    507602                    ds = null;
    508603                }
    509604            }
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java

     
    55import static org.openstreetmap.josm.tools.I18n.trc;
    66
    77import java.io.File;
    8 import java.util.Arrays;
    98import java.util.Collection;
    109import java.util.Collections;
    1110import java.util.EnumSet;
     
    2221import org.openstreetmap.josm.data.osm.Tag;
    2322import org.openstreetmap.josm.data.preferences.BooleanProperty;
    2423import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
    25 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    26 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
    2724import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
    2825import org.openstreetmap.josm.gui.util.LruCache;
    2926import org.openstreetmap.josm.tools.ImageProvider;
     
    4441     */
    4542    protected static final BooleanProperty DISPLAY_KEYS_AS_HINT = new BooleanProperty("taggingpreset.display-keys-as-hint", true);
    4643
    47     protected void initAutoCompletionField(AutoCompletingTextField field, String... key) {
    48         initAutoCompletionField(field, Arrays.asList(key));
    49     }
    50 
    51     protected void initAutoCompletionField(AutoCompletingTextField field, List<String> keys) {
    52         DataSet data = OsmDataManager.getInstance().getEditDataSet();
    53         if (data == null) {
    54             return;
    55         }
    56         AutoCompletionList list = new AutoCompletionList();
    57         AutoCompletionManager.of(data).populateWithTagValues(list, keys);
    58         field.setAutoCompletionList(list);
    59     }
    60 
    6144    /**
    6245     * Returns all cached {@link AutoCompletionItem}s for given keys.
    6346     *
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresets.java

     
    4545    /** cache for key/value pairs found in the preset */
    4646    private static final MultiMap<String, String> PRESET_TAG_CACHE = new MultiMap<>();
    4747    /** cache for roles found in the preset */
    48     private static final Set<String> PRESET_ROLE_CACHE = new HashSet<>();
     48    private static final Map<String, Role> PRESET_ROLE_CACHE = new HashMap<>();
    4949
    5050    /** The collection of listeners */
    5151    private static final Collection<TaggingPresetListener> listeners = new ArrayList<>();
     
    167167            Roles r = (Roles) item;
    168168            for (Role i : r.roles) {
    169169                if (i.key != null) {
    170                     PRESET_ROLE_CACHE.add(i.key);
     170                    PRESET_ROLE_CACHE.put(i.key, i);
    171171                }
    172172            }
    173173        } else if (item instanceof CheckGroup) {
     
    189189     * Replies a set of all roles in the tagging presets.
    190190     * @return a set of all roles in the tagging presets.
    191191     */
    192     public static Set<String> getPresetRoles() {
    193         return Collections.unmodifiableSet(PRESET_ROLE_CACHE);
     192    public static Set<Role> getPresetRoles() {
     193        return new HashSet<>(PRESET_ROLE_CACHE.values());
    194194    }
    195195
    196196    /**
  • src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java

     
    178178        }
    179179
    180180        @Override
     181        public int hashCode() {
     182            final int prime = 31;
     183            int result = 1;
     184            result = prime * result + ((key == null) ? 0 : key.hashCode());
     185            result = prime * result + ((types == null) ? 0 : types.hashCode());
     186            return result;
     187        }
     188
     189        @Override
     190        public boolean equals(Object obj) {
     191            if (this == obj)
     192                return true;
     193            if (obj == null)
     194                return false;
     195            if (getClass() != obj.getClass())
     196                return false;
     197            Role other = (Role) obj;
     198            if (key == null) {
     199                if (other.key != null)
     200                    return false;
     201            } else if (!key.equals(other.key))
     202                return false;
     203            if (types == null) {
     204                if (other.types != null)
     205                    return false;
     206            } else if (!types.equals(other.types))
     207                return false;
     208            return true;
     209        }
     210
     211        @Override
    181212        public String toString() {
    182213            return "Role [key=" + key + ", text=" + text + ']';
    183214        }
  • src/org/openstreetmap/josm/gui/widgets/HistoryComboBox.java

     
    3939     * @return the items as strings
    4040     * @deprecated Has been moved to the model, where it belongs. Use
    4141     *     {@link HistoryComboBoxModel#asStringList} instead. Probably you want to use
    42      *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#load} and
    43      *     {@link org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel.Preferences#save}.
     42     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#load} and
     43     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#save}.
    4444     */
    4545    @Deprecated
    4646    public List<String> getHistory() {
  • src/org/openstreetmap/josm/tools/MultiMap.java

     
    124124    }
    125125
    126126    /**
     127     * Like getValues, but returns all values for all keys.
     128     * @return the set of all values or an empty set
     129     */
     130    public Set<B> getAllValues() {
     131        return map.entrySet().stream().flatMap(e -> e.getValue().stream()).collect(Collectors.toSet());
     132    }
     133
     134    /**
    127135     * Returns {@code true} if this map contains no key-value mappings.
    128136     * @return {@code true} if this map contains no key-value mappings
    129137     * @see Map#isEmpty()
  • test/unit/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditorTest.java

     
    1818import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1919import org.openstreetmap.josm.data.osm.Relation;
    2020import org.openstreetmap.josm.data.osm.Way;
     21import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
    2122import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2223import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
    23 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
     24import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
    2425import org.openstreetmap.josm.testutils.JOSMTestRules;
    2526import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker;
    2627
     
    123124        OsmDataLayer layer = new OsmDataLayer(ds, "test", null);
    124125        IRelationEditor re = newRelationEditor(relation, layer);
    125126
    126         AutoCompletingTextField tfRole = GenericRelationEditor.buildRoleTextField(re);
    127         assertNotNull(tfRole);
     127        AutoCompComboBox<AutoCompletionItem> cbRole = GenericRelationEditor.buildRoleComboBox();
     128        assertNotNull(cbRole);
    128129
    129130        TagEditorPanel tagEditorPanel = new TagEditorPanel(relation, null);
    130131
  • test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorActionTest.java

     
    1212import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1313import org.openstreetmap.josm.data.osm.Relation;
    1414import org.openstreetmap.josm.data.osm.Tag;
     15import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
    1516import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditorTest;
    1617import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor;
    1718import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
     
    2021import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
    2122import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2223import org.openstreetmap.josm.gui.tagging.TagEditorModel;
    23 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
     24import org.openstreetmap.josm.gui.tagging.ac.AutoCompEvent;
     25import org.openstreetmap.josm.gui.tagging.ac.AutoCompListener;
     26import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField;
    2427import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    2528import org.openstreetmap.josm.testutils.JOSMTestRules;
    2629
     
    3134 * @author Michael Zangl
    3235 */
    3336@Disabled
    34 public abstract class AbstractRelationEditorActionTest {
     37public abstract class AbstractRelationEditorActionTest implements AutoCompListener {
    3538    /**
    3639     * Platform for tooltips.
    3740     */
     
    4649    private IRelationEditor editor;
    4750    private MemberTable memberTable;
    4851    private MemberTableModel memberTableModel;
    49     private AutoCompletingTextField tfRole;
     52    private AutoCompTextField<AutoCompletionItem> tfRole;
    5053    private TagEditorModel tagModel;
    5154
    5255    protected final IRelationEditorActionAccess relationEditorAccess = new IRelationEditorActionAccess() {
    5356
    5457        @Override
    55         public AutoCompletingTextField getTextFieldRole() {
     58        public AutoCompTextField<AutoCompletionItem> getTextFieldRole() {
    5659            return tfRole;
    5760        }
    5861
     
    109112        selectionTableModel = new SelectionTableModel(layer);
    110113        selectionTable = new SelectionTable(selectionTableModel, memberTableModel);
    111114        editor = GenericRelationEditorTest.newRelationEditor(orig, layer);
    112         tfRole = new AutoCompletingTextField();
     115        tfRole = new AutoCompTextField<>();
    113116        tagModel = new TagEditorModel();
    114         memberTable = new MemberTable(layer, editor.getRelation(), memberTableModel);
     117        memberTable = new MemberTable(layer, this, memberTableModel);
    115118    }
     119
     120    @Override
     121    public void autoCompBefore(AutoCompEvent e) {
     122    }
     123
     124    @Override
     125    public void autoCompPerformed(AutoCompEvent e) {
     126    }
    116127}