Ticket #21227: 21227-4.patch

File 21227-4.patch, 125.9 KB (added by marcello@…, 4 years ago)

[PATCH] better autocompletion suggestions for all tables in relation editor

  • resources/images/in_dataset.svg

     
     1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
     2<svg
     3   version="1.1"
     4   width="16px"
     5   height="16px"
     6   id="svg79288"
     7   sodipodi:docname="in_dataset.svg"
     8   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
     9   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
     10   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
     11   xmlns="http://www.w3.org/2000/svg"
     12   xmlns:svg="http://www.w3.org/2000/svg">
     13  <defs
     14     id="defs79292" />
     15  <sodipodi:namedview
     16     id="namedview79290"
     17     pagecolor="#ffffff"
     18     bordercolor="#666666"
     19     borderopacity="1.0"
     20     inkscape:pageshadow="2"
     21     inkscape:pageopacity="0.0"
     22     inkscape:pagecheckerboard="0"
     23     showgrid="true"
     24     inkscape:zoom="89.875"
     25     inkscape:cx="3.3769124"
     26     inkscape:cy="8.0055633"
     27     inkscape:window-width="3840"
     28     inkscape:window-height="2085"
     29     inkscape:window-x="0"
     30     inkscape:window-y="0"
     31     inkscape:window-maximized="1"
     32     inkscape:current-layer="svg79288">
     33    <inkscape:grid
     34       type="xygrid"
     35       id="grid79357"
     36       empspacing="8"
     37       dotted="false"
     38       originx="8"
     39       originy="8"
     40       spacingx="0.5"
     41       spacingy="0.5" />
     42  </sodipodi:namedview>
     43  <path
     44     style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
     45     d="M 3,3 13,6 5,13 Z"
     46     id="path80027"
     47     sodipodi:nodetypes="cccc" />
     48  <path
     49     style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#df421e;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
     50     d="m 11.5,4.5 h 3 v 3 h -3 z"
     51     id="path79392" />
     52  <path
     53     style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#df421e;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
     54     d="m 1.5,1.5 h 3 v 3 h -3 z"
     55     id="path79392-7" />
     56  <path
     57     style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#df421e;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
     58     d="m 3.5,11.5 h 3 v 3 h -3 z"
     59     id="path79392-4" />
     60</svg>
  • resources/images/in_standard.svg

     
     1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
     2<svg
     3   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
     4   sodipodi:docname="in_standard.svg"
     5   id="svg12"
     6   version="1.1"
     7   width="16"
     8   viewBox="0 0 16 16"
     9   height="16"
     10   enable-background="new 0 0 96 96"
     11   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
     12   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
     13   xmlns="http://www.w3.org/2000/svg"
     14   xmlns:svg="http://www.w3.org/2000/svg"
     15   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     16   xmlns:cc="http://creativecommons.org/ns#"
     17   xmlns:dc="http://purl.org/dc/elements/1.1/">
     18  <metadata
     19     id="metadata18">
     20    <rdf:RDF>
     21      <cc:Work
     22         rdf:about="">
     23        <dc:format>image/svg+xml</dc:format>
     24        <dc:type
     25           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
     26      </cc:Work>
     27    </rdf:RDF>
     28  </metadata>
     29  <defs
     30     id="defs16" />
     31  <sodipodi:namedview
     32     inkscape:current-layer="svg12"
     33     inkscape:window-maximized="1"
     34     inkscape:window-y="0"
     35     inkscape:window-x="0"
     36     inkscape:cy="9.5865506"
     37     inkscape:cx="7.8530512"
     38     inkscape:zoom="66.916666"
     39     showgrid="true"
     40     id="namedview14"
     41     inkscape:window-height="2085"
     42     inkscape:window-width="3840"
     43     inkscape:pageshadow="2"
     44     inkscape:pageopacity="0"
     45     guidetolerance="10"
     46     gridtolerance="10"
     47     objecttolerance="10"
     48     borderopacity="1"
     49     bordercolor="#666666"
     50     pagecolor="#ffffff"
     51     inkscape:pagecheckerboard="0"
     52     inkscape:lockguides="false">
     53    <inkscape:grid
     54       id="grid843"
     55       type="xygrid"
     56       originx="0"
     57       originy="0"
     58       empspacing="4" />
     59  </sodipodi:namedview>
     60  <text
     61     xml:space="preserve"
     62     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
     63     x="11.570769"
     64     y="11.741786"
     65     id="text12471"><tspan
     66       sodipodi:role="line"
     67       id="tspan12469"
     68       x="11.570769"
     69       y="11.741786"></tspan></text>
     70  <text
     71     xml:space="preserve"
     72     style="font-size:13.3333px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#df421e;fill-opacity:1;stroke:#df421e;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
     73     x="8.0417976"
     74     y="11.405445"
     75     id="text59954"><tspan
     76       sodipodi:role="line"
     77       id="tspan59952"
     78       x="8.0417976"
     79       y="11.405445">§</tspan></text>
     80</svg>
  • src/org/openstreetmap/josm/actions/UploadAction.java

     
    240240        ChangesetUpdater.check();
    241241
    242242        final UploadDialog dialog = UploadDialog.getUploadDialog();
    243         dialog.setUploadedPrimitives(apiData);
    244         dialog.initLifeCycle(layer.getDataSet());
     243        dialog.initLifeCycle(layer.getDataSet(), apiData);
    245244        dialog.setVisible(true);
    246245        dialog.rememberUserInput();
    247246        if (dialog.isCanceled()) {
  • src/org/openstreetmap/josm/data/tagging/ac/AutoCompItemCellRenderer.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.tagging.ac;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.Color;
     7import java.awt.Component;
     8import java.awt.Font;
     9import java.util.Map;
     10
     11import javax.swing.ImageIcon;
     12import javax.swing.JLabel;
     13import javax.swing.JList;
     14import javax.swing.ListCellRenderer;
     15
     16import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer;
     17import org.openstreetmap.josm.tools.ImageProvider;
     18import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
     19
     20/**
     21 * A custom list cell renderer for autocompletion items that colorizes and adds the value count to
     22 * some items.
     23 * <p>
     24 * See also: {@link AutoCompletionPriority#compareTo}
     25 */
     26public class AutoCompItemCellRenderer extends JosmListCellRenderer<AutoCompletionItem> {
     27    /** The color used to render items found in the dataset. */
     28    public static final Color BGCOLOR_1 = new Color(254, 226, 214);
     29    /** The color used to render items found in the standard */
     30    public static final Color BGCOLOR_2 = new Color(235, 255, 177);
     31
     32    protected Map<String, Integer> map;
     33    private static final ImageIcon iconEmpty = ImageProvider.getEmpty(ImageSizes.POPUPMENU);
     34    private static final ImageIcon iconDataSet = ImageProvider.get("in_dataset", ImageSizes.POPUPMENU);
     35    private static final ImageIcon iconStandard = ImageProvider.get("in_standard", ImageSizes.POPUPMENU);
     36
     37    /**
     38     * Constructs the cell renderer.
     39     *
     40     * @param component The component the renderer is attached to. JComboBox or JList.
     41     * @param renderer The L&amp;F renderer. Usually obtained by calling {@code getRenderer()} on {@code component}.
     42     * @param map A map from key to count.
     43     */
     44    public AutoCompItemCellRenderer(Component component, ListCellRenderer<? super AutoCompletionItem> renderer, Map<String, Integer> map) {
     45        super(component, renderer);
     46        this.map = map;
     47    }
     48
     49    @Override
     50    public Component getListCellRendererComponent(JList<? extends AutoCompletionItem> list, AutoCompletionItem value,
     51                                                int index, boolean isSelected, boolean cellHasFocus) {
     52        Integer count = null;
     53        if (value == null)
     54            value = new AutoCompletionItem("", AutoCompletionPriority.IS_IN_STANDARD);
     55
     56        // if there is a value count add it to the text
     57        if (map != null) {
     58            String text = value.toString();
     59            count = map.get(text);
     60            if (count != null) {
     61                value = new AutoCompletionItem(tr("{0} ({1})", text, count), value.getPriority());
     62            }
     63        }
     64
     65        JLabel l = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
     66        l.setIcon(iconEmpty);
     67        if (value.getPriority().isInDataSet()) {
     68            l.setIcon(iconDataSet);
     69        }
     70        if (value.getPriority().isInStandard()) {
     71            l.setIcon(iconStandard);
     72        }
     73        l.setComponentOrientation(component.getComponentOrientation());
     74        if (count != null) {
     75            l.setFont(l.getFont().deriveFont(Font.ITALIC + Font.BOLD));
     76        }
     77        return l;
     78    }
     79}
  • src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java

     
    1010import java.awt.FlowLayout;
    1111import java.awt.GridBagConstraints;
    1212import java.awt.GridBagLayout;
     13import java.awt.Insets;
    1314import java.awt.Window;
    1415import java.awt.datatransfer.Clipboard;
    1516import java.awt.datatransfer.FlavorListener;
    1617import java.awt.event.ActionEvent;
    17 import java.awt.event.FocusAdapter;
    18 import java.awt.event.FocusEvent;
    1918import java.awt.event.InputEvent;
    2019import java.awt.event.KeyEvent;
    2120import java.awt.event.MouseAdapter;
     
    2726import java.util.Collection;
    2827import java.util.Collections;
    2928import java.util.EnumSet;
     29import java.util.HashMap;
    3030import java.util.List;
     31import java.util.Map;
    3132import java.util.Set;
    3233import java.util.stream.Collectors;
    3334
     
    3536import javax.swing.BorderFactory;
    3637import javax.swing.InputMap;
    3738import javax.swing.JButton;
     39import javax.swing.JCheckBox;
    3840import javax.swing.JComponent;
    3941import javax.swing.JLabel;
    4042import javax.swing.JMenuItem;
     
    4547import javax.swing.JSplitPane;
    4648import javax.swing.JTabbedPane;
    4749import javax.swing.JTable;
     50import javax.swing.JTextField;
    4851import javax.swing.JToolBar;
    4952import javax.swing.KeyStroke;
    5053
     
    5861import org.openstreetmap.josm.data.osm.Relation;
    5962import org.openstreetmap.josm.data.osm.RelationMember;
    6063import org.openstreetmap.josm.data.osm.Tag;
     64import org.openstreetmap.josm.data.tagging.ac.AutoCompItemCellRenderer;
     65import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
     66import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority;
    6167import org.openstreetmap.josm.data.validation.tests.RelationChecker;
    6268import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    6369import org.openstreetmap.josm.gui.MainApplication;
     
    98104import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    99105import org.openstreetmap.josm.gui.tagging.TagEditorModel;
    100106import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
    101 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    102 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
     107import org.openstreetmap.josm.gui.tagging.TagTable;
     108import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
     109import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
    103110import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
     111import org.openstreetmap.josm.gui.tagging.ac.DefaultAutoCompListener;
    104112import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
    105113import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    106114import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
     
    108116import org.openstreetmap.josm.gui.util.WindowGeometry;
    109117import org.openstreetmap.josm.spi.preferences.Config;
    110118import org.openstreetmap.josm.tools.CheckParameterUtil;
     119import org.openstreetmap.josm.tools.GBC;
    111120import org.openstreetmap.josm.tools.InputMapUtils;
    112121import org.openstreetmap.josm.tools.Logging;
    113122import org.openstreetmap.josm.tools.Shortcut;
     
    118127 * @since 343
    119128 */
    120129public class GenericRelationEditor extends RelationEditor implements CommandQueueListener {
     130    private static final String PREF_LASTROLE = "relation.editor.generic.lastrole";
     131    private static final String PREF_USE_ROLE_FILTER = "relation.editor.use_role_filter";
     132
    121133    /** the tag table and its model */
    122134    private final TagEditorPanel tagEditorPanel;
    123135    private final ReferringRelationsBrowser referrerBrowser;
     
    131143    private final SelectionTable selectionTable;
    132144    private final SelectionTableModel selectionTableModel;
    133145
    134     private final AutoCompletingTextField tfRole;
     146    private final AutoCompletionManager manager;
     147    private final AutoCompComboBox<AutoCompletionItem> cbRole;
    135148
    136149    /**
    137150     * the menu item in the windows menu. Required to properly hide on dialog close.
     
    172185
    173186    private Component selectedTabPane;
    174187    private JTabbedPane tabbedPane;
     188    private JCheckBox btnFilter = new JCheckBox(tr("Filter"));
    175189
    176190    /**
    177191     * Creates a new relation editor for the given relation. The relation will be saved if the user
     
    212226        selectionTableModel.register();
    213227        referrerModel = new ReferringRelationsBrowserModel(relation);
    214228
     229        manager = AutoCompletionManager.of(this.getLayer().data);
     230
    215231        tagEditorPanel = new TagEditorPanel(relation, presetHandler);
     232        TagTable tagTable = tagEditorPanel.getTable();
    216233        populateModels(relation);
    217234        tagEditorPanel.getModel().ensureOneTag();
    218235
     236        // setting up the tag table
     237        AutoCompComboBox<AutoCompletionItem> keyEditor = new AutoCompComboBox<>();
     238        AutoCompComboBox<AutoCompletionItem> valueEditor = new AutoCompComboBox<>();
     239        KeyAutoCompManager keyAutoCompManager = new KeyAutoCompManager();
     240        ValueAutoCompManager valueAutoCompManager = new ValueAutoCompManager();
     241        keyEditor.getEditorComponent().setMaxTextLength(256);
     242        keyEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
     243        keyEditor.getEditorComponent().enableUndoRedo(false);
     244        keyEditor.getEditorComponent().addAutoCompListener(keyAutoCompManager);
     245        keyEditor.addPopupMenuListener(keyAutoCompManager);
     246        keyEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
     247        keyEditor.setRenderer(new AutoCompItemCellRenderer(keyEditor, keyEditor.getRenderer(), null));
     248
     249        valueEditor.getEditorComponent().setMaxTextLength(-1);
     250        valueEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
     251        valueEditor.getEditorComponent().enableUndoRedo(false);
     252        valueEditor.getEditorComponent().addAutoCompListener(valueAutoCompManager);
     253        valueEditor.addPopupMenuListener(valueAutoCompManager);
     254        valueEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
     255        valueEditor.setRenderer(new AutoCompItemCellRenderer(valueEditor, valueEditor.getRenderer(), null));
     256
     257        tagTable.setRowHeight(keyEditor.getEditorComponent().getPreferredSize().height);
     258        tagTable.setKeyEditor(keyEditor);
     259        tagTable.setValueEditor(valueEditor);
     260
    219261        // setting up the member table
    220         memberTable = new MemberTable(getLayer(), getRelation(), memberTableModel);
     262        AutoCompComboBox<AutoCompletionItem> cbRoleEditor = new AutoCompComboBox<>();
     263        RoleAutoCompManager roleAutoCompManager = new RoleAutoCompManager();
     264        cbRoleEditor.getEditorComponent().addAutoCompListener(roleAutoCompManager);
     265        cbRoleEditor.addPopupMenuListener(roleAutoCompManager);
     266        cbRoleEditor.getEditorComponent().enableUndoRedo(false);
     267        Insets insets = cbRoleEditor.getEditorComponent().getInsets();
     268        cbRoleEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(0, insets.left, 0, insets.right));
     269        cbRoleEditor.setToolTipText(tr("Select a role for this relation member"));
     270        cbRoleEditor.setRenderer(new AutoCompItemCellRenderer(cbRoleEditor, cbRoleEditor.getRenderer(), null));
     271
     272        int height = cbRoleEditor.getEditorComponent().getPreferredSize().height;
     273        memberTable = new MemberTable(getLayer(), cbRoleEditor, memberTableModel);
    221274        memberTable.addMouseListener(new MemberTableDblClickAdapter());
     275        memberTable.setRowHeight(height);
    222276        memberTableModel.addMemberModelListener(memberTable);
    223277
    224         MemberRoleCellEditor ce = (MemberRoleCellEditor) memberTable.getColumnModel().getColumn(0).getCellEditor();
    225278        selectionTable = new SelectionTable(selectionTableModel, memberTableModel);
    226         selectionTable.setRowHeight(ce.getEditor().getPreferredSize().height);
     279        selectionTable.setRowHeight(height);
    227280
    228281        LeftButtonToolbar leftButtonToolbar = new LeftButtonToolbar(new RelationEditorActionAccess());
    229         tfRole = buildRoleTextField(this);
     282        cbRole = new AutoCompComboBox<>();
     283        cbRole.getEditorComponent().addAutoCompListener(roleAutoCompManager);
     284        cbRole.addPopupMenuListener(roleAutoCompManager);
     285        cbRole.setText(Config.getPref().get(PREF_LASTROLE, ""));
     286        cbRole.setToolTipText(tr("Select a role"));
     287        cbRole.setRenderer(new AutoCompItemCellRenderer(cbRole, cbRole.getRenderer(), null));
    230288
    231289        JSplitPane pane = buildSplitPane(
    232290                buildTagEditorPanel(tagEditorPanel),
    233                 buildMemberEditorPanel(leftButtonToolbar, new RelationEditorActionAccess()),
     291                buildMemberEditorPanel(leftButtonToolbar),
    234292                this);
    235293        pane.setPreferredSize(new Dimension(100, 100));
    236294
     
    310368                @Override
    311369                public void actionPerformed(ActionEvent e) {
    312370                    super.actionPerformed(e);
    313                     tfRole.requestFocusInWindow();
     371                    cbRole.requestFocusInWindow();
    314372                }
    315373            }, "PASTE_MEMBERS", key, getRootPane(), memberTable, selectionTable);
    316374        }
     
    446504    }
    447505
    448506    /**
    449      * builds the role text field
    450      * @param re relation editor
    451      * @return the role text field
    452      */
    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() {
    457             @Override
    458             public void focusGained(FocusEvent e) {
    459                 tfRole.selectAll();
    460             }
    461         });
    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;
    477     }
    478 
    479     /**
    480507     * builds the panel for the relation member editor
    481508     * @param leftButtonToolbar left button toolbar
    482      * @param editorAccess The relation editor
    483509     *
    484510     * @return the panel for the relation member editor
    485511     */
    486     protected static JPanel buildMemberEditorPanel(
    487             LeftButtonToolbar leftButtonToolbar, IRelationEditorActionAccess editorAccess) {
     512    protected JPanel buildMemberEditorPanel(LeftButtonToolbar leftButtonToolbar) {
    488513        final JPanel pnl = new JPanel(new GridBagLayout());
    489         final JScrollPane scrollPane = new JScrollPane(editorAccess.getMemberTable());
     514        final JScrollPane scrollPane = new JScrollPane(memberTable);
    490515
    491516        GridBagConstraints gc = new GridBagConstraints();
    492517        gc.gridx = 0;
     
    518543        pnl.add(scrollPane, gc);
    519544
    520545        // --- role editing
    521         JPanel p3 = new JPanel(new FlowLayout(FlowLayout.LEFT));
    522         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)
     546        JPanel p3 = new JPanel(new GridBagLayout());
     547        GBC gbc = GBC.std().fill(GridBagConstraints.NONE);
     548        JLabel lbl = new JLabel(tr("Role:"));
     549        p3.add(lbl, gbc);
     550
     551        p3.add(cbRole, gbc.insets(3, 3, 0, 3).fill(GridBagConstraints.HORIZONTAL));
     552
     553        SetRoleAction setRoleAction = new SetRoleAction(new RelationEditorActionAccess());
     554        memberTableModel.getSelectionModel().addListSelectionListener(setRoleAction);
     555        cbRole.getEditorComponent().getDocument().addDocumentListener(setRoleAction);
     556        cbRole.getEditorComponent().addActionListener(setRoleAction);
     557        memberTableModel.getSelectionModel().addListSelectionListener(
     558                e -> cbRole.setEnabled(memberTable.getSelectedRowCount() > 0)
    530559        );
    531         editorAccess.getTextFieldRole().setEnabled(editorAccess.getMemberTable().getSelectedRowCount() > 0);
     560        cbRole.setEnabled(memberTable.getSelectedRowCount() > 0);
     561
    532562        JButton btnApply = new JButton(setRoleAction);
    533         btnApply.setPreferredSize(new Dimension(20, 20));
     563        int height = cbRole.getPreferredSize().height;
     564        btnApply.setPreferredSize(new Dimension(height, height));
    534565        btnApply.setText("");
    535         p3.add(btnApply);
     566        p3.add(btnApply, gbc.weight(0, 0).fill(GridBagConstraints.NONE));
    536567
     568        btnFilter.setToolTipText(tr("Filter suggestions based on context"));
     569        btnFilter.setSelected(Config.getPref().getBoolean(PREF_USE_ROLE_FILTER, false));
     570        p3.add(btnFilter, gbc.span(GridBagConstraints.REMAINDER));
     571
     572        //
     573
    537574        gc.gridx = 1;
    538575        gc.gridy = 2;
    539576        gc.fill = GridBagConstraints.HORIZONTAL;
     
    562599        gc.anchor = GridBagConstraints.NORTHWEST;
    563600        gc.weightx = 0.0;
    564601        gc.weighty = 1.0;
    565         pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar(editorAccess),
     602        pnl2.add(new ScrollViewport(buildSelectionControlButtonToolbar(new RelationEditorActionAccess()),
    566603                ScrollViewport.VERTICAL_DIRECTION), gc);
    567604
    568605        gc.gridx = 1;
     
    570607        gc.weightx = 1.0;
    571608        gc.weighty = 1.0;
    572609        gc.fill = GridBagConstraints.BOTH;
    573         pnl2.add(buildSelectionTablePanel(editorAccess.getSelectionTable()), gc);
     610        pnl2.add(buildSelectionTablePanel(selectionTable), gc);
    574611
    575612        final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
    576613        splitPane.setLeftComponent(pnl);
    577614        splitPane.setRightComponent(pnl2);
    578615        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         }
     616        addWindowListener(new WindowAdapter() {
     617            @Override
     618            public void windowOpened(WindowEvent e) {
     619                // has to be called when the window is visible, otherwise no effect
     620                splitPane.setDividerLocation(0.6);
     621            }
     622        });
    588623
    589624        JPanel pnl3 = new JPanel(new BorderLayout());
    590625        pnl3.add(splitPane, BorderLayout.CENTER);
     
    739774        if (isVisible() == visible) {
    740775            return;
    741776        }
    742         if (visible) {
    743             tagEditorPanel.initAutoCompletion(getLayer());
    744         }
    745777        super.setVisible(visible);
    746778        Clipboard clipboard = ClipboardUtils.getClipboard();
    747779        if (visible) {
     
    754786                clipboard.addFlavorListener(listener);
    755787            }
    756788        } else {
     789            Config.getPref().put(PREF_LASTROLE, cbRole.getText());
     790            Config.getPref().putBoolean(PREF_USE_ROLE_FILTER, btnFilter.isSelected());
     791
    757792            // make sure all registered listeners are unregistered
    758793            //
    759794            memberTable.stopHighlighting();
     
    10391074        }
    10401075
    10411076        @Override
    1042         public AutoCompletingTextField getTextFieldRole() {
    1043             return tfRole;
     1077        public JTextField getTextFieldRole() {
     1078            return cbRole.getEditorComponent();
    10441079        }
    1045 
    10461080    }
    10471081
    10481082    @Override
     
    10541088            applyAction.updateEnabledState();
    10551089        }
    10561090    }
     1091
     1092    private class KeyAutoCompManager extends DefaultAutoCompListener<AutoCompletionItem> {
     1093        @Override
     1094        protected void updateAutoCompModel(AutoCompComboBoxModel<AutoCompletionItem> model) {
     1095            Map<String, AutoCompletionPriority> map;
     1096            Map<String, String> keys = tagEditorPanel.getModel().getTags();
     1097            Map<String, String> matchKeys = btnFilter.isSelected() ? keys : null;
     1098
     1099            map = AutoCompletionManager.merge(
     1100                manager.getKeysForRelation(matchKeys),
     1101                manager.getPresetKeys(EnumSet.of(TaggingPresetType.RELATION), matchKeys)
     1102            );
     1103
     1104            model.replaceAllElements(map.entrySet().stream().filter(e -> !keys.containsKey(e.getKey()))
     1105                .map(e -> new AutoCompletionItem(e.getKey(), e.getValue()))
     1106                .sorted(AutoCompletionManager.ALPHABETIC_COMPARATOR).collect(Collectors.toList()));
     1107        }
     1108    }
     1109
     1110    private class ValueAutoCompManager extends DefaultAutoCompListener<AutoCompletionItem> {
     1111        @Override
     1112        protected void updateAutoCompModel(AutoCompComboBoxModel<AutoCompletionItem> model) {
     1113            Map<String, AutoCompletionPriority> map;
     1114            Map<String, String> keys = btnFilter.isSelected() ? tagEditorPanel.getModel().getTags() : null;
     1115            String key = (String) tagEditorPanel.getModel().getValueAt(tagEditorPanel.getTable().getEditingRow(), 0);
     1116
     1117            map = AutoCompletionManager.merge(
     1118                manager.getValuesForRelation(keys, key),
     1119                manager.getPresetValues(EnumSet.of(TaggingPresetType.RELATION), keys, key)
     1120            );
     1121
     1122            model.replaceAllElements(map.entrySet().stream()
     1123                .map(e -> new AutoCompletionItem(e.getKey(), e.getValue()))
     1124                .sorted(AutoCompletionManager.ALPHABETIC_COMPARATOR).collect(Collectors.toList()));
     1125        }
     1126    }
     1127
     1128    /**
     1129     * Returns the roles currently edited in the members table.
     1130     * @param types the preset types to include, (node / way / relation ...) or null to include all types
     1131     * @return the roles currently edited in the members table.
     1132     */
     1133    private Map<String, AutoCompletionPriority> getCurrentRoles(Collection<TaggingPresetType> types) {
     1134        Map<String, AutoCompletionPriority> map = new HashMap<>();
     1135        for (int i = 0; i < memberTableModel.getRowCount(); ++i) {
     1136            RelationMember member = memberTableModel.getValue(i);
     1137            if (types == null || types.contains(TaggingPresetType.forPrimitiveType(member.getDisplayType()))) {
     1138                map.merge(member.getRole(), AutoCompletionPriority.IS_IN_SELECTION, AutoCompletionPriority::mergeWith);
     1139            }
     1140        }
     1141        return map;
     1142    }
     1143
     1144    private class RoleAutoCompManager extends DefaultAutoCompListener<AutoCompletionItem> {
     1145        @Override
     1146        protected void updateAutoCompModel(AutoCompComboBoxModel<AutoCompletionItem> model) {
     1147            Map<String, AutoCompletionPriority> map;
     1148            Map<String, String> keys = btnFilter.isSelected() ? tagEditorPanel.getModel().getTags() : null;
     1149
     1150            EnumSet<TaggingPresetType> selectedTypes = EnumSet.noneOf(TaggingPresetType.class);
     1151            for (RelationMember member : memberTableModel.getSelectedMembers()) {
     1152                selectedTypes.add(TaggingPresetType.forPrimitiveType(member.getDisplayType()));
     1153            }
     1154
     1155            map = AutoCompletionManager.merge(
     1156                manager.getRolesForRelation(keys, selectedTypes),
     1157                manager.getPresetRoles(keys, selectedTypes),
     1158                getCurrentRoles(selectedTypes)
     1159            );
     1160
     1161            // turn into AutoCompletionItems
     1162            model.replaceAllElements(map.entrySet().stream()
     1163                .map(e -> new AutoCompletionItem(e.getKey(), e.getValue()))
     1164                .sorted(AutoCompletionManager.ALPHABETIC_COMPARATOR).collect(Collectors.toList()));
     1165        }
     1166    }
    10571167}
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.dialogs.relation;
    3 
    4 import java.awt.Component;
    5 
    6 import javax.swing.AbstractCellEditor;
    7 import javax.swing.BorderFactory;
    8 import javax.swing.CellEditor;
    9 import javax.swing.JTable;
    10 import javax.swing.table.TableCellEditor;
    11 
    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;
    16 
    17 /**
    18  * The {@link CellEditor} for the role cell in the table. Supports autocompletion.
    19  */
    20 public class MemberRoleCellEditor extends AbstractCellEditor implements TableCellEditor {
    21     private final AutoCompletingTextField editor;
    22     private final AutoCompletionManager autoCompletionManager;
    23     private final transient Relation relation;
    24 
    25     /** user input is matched against this list of auto completion items */
    26     private final AutoCompletionList autoCompletionList;
    27 
    28     /**
    29      * Constructs a new {@code MemberRoleCellEditor}.
    30      * @param autoCompletionManager the auto completion manager. Must not be null
    31      * @param relation the relation. Can be null
    32      * @since 13675
    33      */
    34     public MemberRoleCellEditor(AutoCompletionManager autoCompletionManager, Relation relation) {
    35         this.autoCompletionManager = autoCompletionManager;
    36         this.relation = relation;
    37         editor = new AutoCompletingTextField(0, false);
    38         editor.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
    39         autoCompletionList = new AutoCompletionList();
    40         editor.setAutoCompletionList(autoCompletionList);
    41     }
    42 
    43     @Override
    44     public Component getTableCellEditorComponent(JTable table,
    45             Object value, boolean isSelected, int row, int column) {
    46 
    47         String role = (String) value;
    48         editor.setText(role);
    49         autoCompletionList.clear();
    50         autoCompletionManager.populateWithMemberRoles(autoCompletionList, relation);
    51         return editor;
    52     }
    53 
    54     @Override
    55     public Object getCellEditorValue() {
    56         return editor.getText();
    57     }
    58 
    59     /**
    60      * Returns the edit field for this cell editor.
    61      * @return the edit field for this cell editor
    62      */
    63     public AutoCompletingTextField getEditor() {
    64         return editor;
    65     }
    66 }
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java

    Property changes on: src/org/openstreetmap/josm/gui/dialogs/relation/MemberRoleCellEditor.java
    ___________________________________________________________________
    Deleted: svn:eol-style
    ## -1 +0,0 ##
    -native
    \ No newline at end of property
     
    2020import javax.swing.SwingUtilities;
    2121import javax.swing.event.ListSelectionEvent;
    2222import javax.swing.event.ListSelectionListener;
     23import javax.swing.table.TableCellEditor;
    2324
    2425import org.openstreetmap.josm.actions.AbstractShowHistoryAction;
    2526import org.openstreetmap.josm.actions.AutoScaleAction;
     
    2728import org.openstreetmap.josm.actions.HistoryInfoAction;
    2829import org.openstreetmap.josm.actions.ZoomToAction;
    2930import org.openstreetmap.josm.data.osm.OsmPrimitive;
    30 import org.openstreetmap.josm.data.osm.Relation;
    3131import org.openstreetmap.josm.data.osm.RelationMember;
    3232import org.openstreetmap.josm.data.osm.Way;
    3333import org.openstreetmap.josm.gui.MainApplication;
     
    4141import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
    4242import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
    4343import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    44 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
    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 roleCellEditor the role editor combobox
    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, TableCellEditor roleCellEditor, MemberTableModel model) {
     66        super(model, new MemberTableColumnModel(roleCellEditor), model.getSelectionModel());
    6867        setLayer(layer);
    6968        model.addMemberModelListener(this);
    7069
    71         MemberRoleCellEditor ce = (MemberRoleCellEditor) getColumnModel().getColumn(0).getCellEditor();
    72         setRowHeight(ce.getEditor().getPreferredSize().height);
     70        setRowHeight(roleCellEditor.getTableCellEditorComponent(this, "", false, 0, 0).getPreferredSize().height);
    7371        setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
    7472        setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    7573        putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
     
    8381        if (!GraphicsEnvironment.isHeadless()) {
    8482            setTransferHandler(new MemberTransferHandler());
    8583            setFillsViewportHeight(true); // allow drop on empty table
    86             if (!GraphicsEnvironment.isHeadless()) {
    87                 setDragEnabled(true);
    88             }
     84            setDragEnabled(true);
    8985            setDropMode(DropMode.INSERT_ROWS);
    9086        }
    9187    }
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableColumnModel.java

     
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    66import javax.swing.table.DefaultTableColumnModel;
     7import javax.swing.table.TableCellEditor;
    78import javax.swing.table.TableColumn;
    89
    9 import org.openstreetmap.josm.data.osm.Relation;
    10 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
    11 
    1210/**
    1311 * This is the column model for the {@link MemberTable}
    1412 */
     
    1614
    1715    /**
    1816     * Constructs a new {@code MemberTableColumnModel}.
    19      * @param autoCompletionManager the auto completion manager. Must not be null
    20      * @param relation the relation. Can be null
     17     * @param roleCellEditor the role editor combobox
    2118     * @since 13675
    2219     */
    23     public MemberTableColumnModel(AutoCompletionManager autoCompletionManager, Relation relation) {
     20    public MemberTableColumnModel(TableCellEditor roleCellEditor) {
    2421        TableColumn col;
    2522
    2623        // column 0 - the member role
     
    2926        col.setResizable(true);
    3027        col.setPreferredWidth(100);
    3128        col.setCellRenderer(new MemberTableRoleCellRenderer());
    32         col.setCellEditor(new MemberRoleCellEditor(autoCompletionManager, relation));
     29        col.setCellEditor(roleCellEditor);
    3330        addColumn(col);
    3431
    3532        // 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}.
     
    3232    public SetRoleAction(IRelationEditorActionAccess editorAccess) {
    3333        super(editorAccess);
    3434        this.tfRole = editorAccess.getTextFieldRole();
    35         putValue(SHORT_DESCRIPTION, tr("Sets a role for the selected members"));
     35        putValue(SHORT_DESCRIPTION, tr("Apply the role to the selected members"));
    3636        new ImageProvider("apply").getResource().attachImageIcon(this);
    3737        putValue(NAME, tr("Apply Role"));
    3838        updateEnabledState();
  • src/org/openstreetmap/josm/gui/io/UploadDialog.java

     
    1818import java.beans.PropertyChangeListener;
    1919import java.lang.Character.UnicodeBlock;
    2020import java.util.ArrayList;
    21 import java.util.Collections;
     21import java.util.Arrays;
    2222import java.util.HashMap;
    2323import java.util.List;
    2424import java.util.Locale;
     
    4343import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
    4444import org.openstreetmap.josm.gui.help.HelpUtil;
    4545import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
     46import org.openstreetmap.josm.gui.tagging.TagTable;
     47import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
     48import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel;
     49import org.openstreetmap.josm.gui.tagging.ac.DefaultAutoCompListener;
    4650import org.openstreetmap.josm.gui.util.GuiHelper;
    4751import org.openstreetmap.josm.gui.util.MultiLineFlowLayout;
    4852import org.openstreetmap.josm.gui.util.WindowGeometry;
     
    8892    /** the model keeping the state of the changeset tags */
    8993    private final transient UploadDialogModel model = new UploadDialogModel();
    9094
    91     private transient DataSet dataSet;
    92 
    9395    /**
    9496     * Constructs a new {@code UploadDialog}.
    9597     */
     
    141143        pnlTagEditor = new TagEditorPanel(model, null, Changeset.MAX_CHANGESET_TAG_LENGTH);
    142144        pnlTagEditorBorder.add(pnlTagEditor, BorderLayout.CENTER);
    143145
     146        // setting up the tag table
     147        TagTable tagTable = pnlTagEditor.getTable();
     148        AutoCompComboBox<String> keyEditor = new AutoCompComboBox<>();
     149        AutoCompComboBox<String> valueEditor = new AutoCompComboBox<>();
     150        KeyAutoCompManager keyAutoCompManager = new KeyAutoCompManager();
     151        ValueAutoCompManager valueAutoCompManager = new ValueAutoCompManager();
     152        keyEditor.getEditorComponent().setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
     153        keyEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
     154        keyEditor.getEditorComponent().enableUndoRedo(false);
     155        keyEditor.getEditorComponent().addAutoCompListener(keyAutoCompManager);
     156        keyEditor.addPopupMenuListener(keyAutoCompManager);
     157        keyEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
     158
     159        valueEditor.getEditorComponent().setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
     160        valueEditor.getEditorComponent().setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
     161        valueEditor.getEditorComponent().enableUndoRedo(false);
     162        valueEditor.getEditorComponent().addAutoCompListener(valueAutoCompManager);
     163        valueEditor.addPopupMenuListener(valueAutoCompManager);
     164        valueEditor.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
     165
     166        tagTable.setRowHeight(keyEditor.getEditorComponent().getPreferredSize().height);
     167        tagTable.setKeyEditor(keyEditor);
     168        tagTable.setValueEditor(valueEditor);
     169
    144170        pnlChangesetManagement = new ChangesetManagementPanel();
    145171        pnlUploadStrategySelectionPanel = new UploadStrategySelectionPanel();
    146172        pnlSettings.add(pnlChangesetManagement, GBC.eop().fill(GridBagConstraints.HORIZONTAL));
     
    228254     * this in the constructor because the dialog is a singleton.
    229255     *
    230256     * @param dataSet The Dataset we want to upload
     257     * @param toUpload The primitves to upload
    231258     * @since 18173
    232259     */
    233     public void initLifeCycle(DataSet dataSet) {
     260    public void initLifeCycle(DataSet dataSet, APIDataSet toUpload) {
    234261        Map<String, String> map = new HashMap<>();
    235         this.dataSet = dataSet;
    236262        pnlBasicUploadSettings.initLifeCycle(map);
    237263        pnlChangesetManagement.initLifeCycle();
    238264        model.clear();
    239         model.putAll(map);          // init with tags from history
    240         model.putAll(this.dataSet); // overwrite with tags from the dataset
     265        model.putAll(map);     // init with tags from history
     266        model.putAll(dataSet); // overwrite with tags from the dataset
    241267
    242268        tpConfigPanels.setSelectedIndex(0);
    243         pnlTagEditor.initAutoCompletion(MainApplication.getLayerManager().getEditLayer());
    244269        pnlUploadStrategySelectionPanel.initFromPreferences();
    245270
    246271        // update the summary
     
    247272        UploadParameterSummaryPanel sumPnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
    248273        sumPnl.setUploadStrategySpecification(pnlUploadStrategySelectionPanel.getUploadStrategySpecification());
    249274        sumPnl.setCloseChangesetAfterNextUpload(pnlChangesetManagement.isCloseChangesetAfterUpload());
    250     }
    251275
    252     /**
    253      * Sets the collection of primitives to upload
    254      *
    255      * @param toUpload the dataset with the objects to upload. If null, assumes the empty
    256      * set of objects to upload
    257      *
    258      */
    259     public void setUploadedPrimitives(APIDataSet toUpload) {
    260         UploadParameterSummaryPanel sumPnl = pnlBasicUploadSettings.getUploadParameterSummaryPanel();
    261         if (toUpload == null) {
    262             if (pnlUploadedObjects != null) {
    263                 List<OsmPrimitive> emptyList = Collections.emptyList();
    264                 pnlUploadedObjects.setUploadedPrimitives(emptyList, emptyList, emptyList);
    265                 sumPnl.setNumObjects(0);
    266             }
    267             return;
    268         }
    269276        List<OsmPrimitive> l = toUpload.getPrimitives();
    270277        pnlBasicUploadSettings.setUploadedPrimitives(l);
    271         pnlUploadedObjects.setUploadedPrimitives(
    272                 toUpload.getPrimitivesToAdd(),
    273                 toUpload.getPrimitivesToUpdate(),
    274                 toUpload.getPrimitivesToDelete()
    275         );
     278        pnlUploadedObjects.removeAll();
     279        pnlUploadedObjects.build(toUpload);
    276280        sumPnl.setNumObjects(l.size());
    277281        pnlUploadStrategySelectionPanel.setNumUploadedObjects(l.size());
    278282    }
     
    512516        }
    513517    }
    514518
     519    private static class KeyAutoCompManager extends DefaultAutoCompListener<String> {
     520        @Override
     521        protected void updateAutoCompModel(AutoCompComboBoxModel<String> model) {
     522            model.replaceAllElements(Arrays.asList("comment", "source", "review_requested", "created_by", "imagery_used", "locale"));
     523            // FIXME add more tags from user upload history?
     524        }
     525    }
     526
     527    private class ValueAutoCompManager extends DefaultAutoCompListener<String> {
     528        @Override
     529        protected void updateAutoCompModel(AutoCompComboBoxModel<String> model) {
     530            String key = (String) pnlTagEditor.getModel().getValueAt(pnlTagEditor.getTable().getEditingRow(), 0);
     531            if ("comment".equals(key)) {
     532                model.prefs(x ->x, x -> x).load(BasicUploadSettingsPanel.COMMENT_HISTORY_KEY);
     533                return;
     534            }
     535            if ("source".equals(key)) {
     536                model.prefs(x -> x, x -> x).load(BasicUploadSettingsPanel.SOURCE_HISTORY_KEY, BasicUploadSettingsPanel.getDefaultSources());
     537                return;
     538            }
     539            if ("review_requested".equals(key)) {
     540                model.replaceAllElements(Arrays.asList("yes", ""));
     541                return;
     542            }
     543            model.replaceAllElements(Arrays.asList(""));
     544        }
     545    }
     546
    515547    /* -------------------------------------------------------------------------- */
    516548    /* Interface PropertyChangeListener                                           */
    517549    /* -------------------------------------------------------------------------- */
     
    604636     * @since 14251
    605637     */
    606638    public void clean() {
    607         setUploadedPrimitives(null);
    608         dataSet = null;
     639        pnlUploadedObjects.removeAll();
    609640    }
    610641}
  • src/org/openstreetmap/josm/gui/io/UploadDialogModel.java

     
    7575     * @return the hashtags separated by ";" or null
    7676     */
    7777    String findHashTags(String comment) {
    78         String hashtags = String.join(";",
     78        String hashTags = String.join(";",
    7979            Arrays.stream(comment.split("\\s", -1))
    8080                .map(s -> Utils.strip(s, ",;"))
    8181                .filter(s -> s.matches("#[a-zA-Z0-9][-_a-zA-Z0-9]+"))
    8282                .collect(Collectors.toList()));
    83         return hashtags.isEmpty() ? null : hashtags;
     83        return hashTags.isEmpty() ? null : hashTags;
    8484    }
    8585
    8686    /**
  • src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java

     
    401401                        // return to the upload dialog
    402402                        //
    403403                        toUpload.removeProcessed(processedPrimitives);
    404                         UploadDialog.getUploadDialog().setUploadedPrimitives(toUpload);
     404                        UploadDialog.getUploadDialog().initLifeCycle(null, toUpload);
    405405                        UploadDialog.getUploadDialog().setVisible(true);
    406406                        break;
    407407                    }
  • src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java

     
    6969
    7070    protected JPanel buildUploadStrategyPanel() {
    7171        JPanel pnl = new JPanel(new GridBagLayout());
    72         pnl.setBorder(BorderFactory.createTitledBorder(tr("Please select the upload strategy:")));
     72        pnl.setBorder(BorderFactory.createTitledBorder(tr("Please select an upload strategy:")));
    7373        ButtonGroup bgStrategies = new ButtonGroup();
    7474        rbStrategy = new EnumMap<>(UploadStrategy.class);
    7575        lblNumRequests = new EnumMap<>(UploadStrategy.class);
  • src/org/openstreetmap/josm/gui/io/UploadedObjectsSummaryPanel.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.io;
    33
    4 import static org.openstreetmap.josm.tools.I18n.tr;
    54import static org.openstreetmap.josm.tools.I18n.trn;
    65
    76import java.awt.GridBagConstraints;
     
    87import java.awt.GridBagLayout;
    98import java.awt.event.MouseAdapter;
    109import java.awt.event.MouseEvent;
    11 import java.util.ArrayList;
    1210import java.util.Collections;
    1311import java.util.List;
    14 import java.util.Optional;
    1512
    16 import javax.swing.AbstractListModel;
     13import javax.swing.BoxLayout;
     14import javax.swing.DefaultListModel;
    1715import javax.swing.JLabel;
    1816import javax.swing.JList;
    1917import javax.swing.JPanel;
     
    2018import javax.swing.JScrollPane;
    2119
    2220import org.openstreetmap.josm.actions.AutoScaleAction;
     21import org.openstreetmap.josm.data.APIDataSet;
    2322import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2423import org.openstreetmap.josm.gui.PrimitiveRenderer;
    2524
     
    2827 * @since 2599
    2928 */
    3029public class UploadedObjectsSummaryPanel extends JPanel {
    31     /** the list with the added primitives */
    32     private PrimitiveList lstAdd;
    33     private JLabel lblAdd;
    34     private JScrollPane spAdd;
    35     /** the list with the updated primitives */
    36     private PrimitiveList lstUpdate;
    37     private JLabel lblUpdate;
    38     private JScrollPane spUpdate;
    39     /** the list with the deleted primitives */
    40     private PrimitiveList lstDelete;
    41     private JLabel lblDelete;
    42     private JScrollPane spDelete;
     30    /**
     31     * Zooms to the primitive on double-click
     32     */
     33    private static MouseAdapter mouseListener = new MouseAdapter() {
     34        @Override
     35        public void mouseClicked(MouseEvent evt) {
     36            if (evt.getButton() == MouseEvent.BUTTON1 && evt.getClickCount() == 2) {
     37                @SuppressWarnings("unchecked")
     38                JList<OsmPrimitive> list = (JList<OsmPrimitive>) evt.getSource();
     39                int index = list.locationToIndex(evt.getPoint());
     40                AutoScaleAction.zoomTo(Collections.singleton(list.getModel().getElementAt(index)));
     41            }
     42        }
     43    };
    4344
    4445    /**
     46     * A JLabel and a JList
     47     */
     48    private static class ListPanel extends JPanel {
     49        /**
     50         * Constructor
     51         *
     52         * @param primitives the list of primitives
     53         * @param singular the singular form of the label
     54         * @param plural the plural form of the label
     55         */
     56        ListPanel(List<OsmPrimitive> primitives, String singular, String plural) {
     57            DefaultListModel<OsmPrimitive> model = new DefaultListModel<>();
     58            JList<OsmPrimitive> jList = new JList<>(model);
     59            primitives.forEach(model::addElement);
     60            jList.setCellRenderer(new PrimitiveRenderer());
     61            jList.addMouseListener(mouseListener);
     62            JScrollPane scrollPane = new JScrollPane(jList);
     63            JLabel label = new JLabel(trn(singular, plural, model.size(), model.size()));
     64            label.setLabelFor(jList);
     65            this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
     66            this.add(label);
     67            this.add(scrollPane);
     68        }
     69    }
     70
     71    /**
    4572     * Constructs a new {@code UploadedObjectsSummaryPanel}.
    4673     */
    4774    public UploadedObjectsSummaryPanel() {
    48         build();
     75        super(new GridBagLayout());
    4976    }
    5077
    51     protected void build() {
    52         setLayout(new GridBagLayout());
    53         PrimitiveRenderer renderer = new PrimitiveRenderer();
    54         MouseAdapter mouseListener = new MouseAdapter() {
    55             @Override
    56             public void mouseClicked(MouseEvent evt) {
    57                 if (evt.getButton() == MouseEvent.BUTTON1 && evt.getClickCount() == 2) {
    58                     PrimitiveList list = (PrimitiveList) evt.getSource();
    59                     int index = list.locationToIndex(evt.getPoint());
    60                     AutoScaleAction.zoomTo(Collections.singleton(list.getModel().getElementAt(index)));
    61                 }
    62             }
    63         };
    64         // initialize the three lists for uploaded primitives, but don't add them to the dialog yet, see setUploadedPrimitives()
    65         //
    66         lstAdd = new PrimitiveList();
    67         lstAdd.setCellRenderer(renderer);
    68         lstAdd.addMouseListener(mouseListener);
    69         lstAdd.setVisibleRowCount(Math.min(lstAdd.getModel().getSize(), 10));
    70         spAdd = new JScrollPane(lstAdd);
    71         lblAdd = new JLabel(tr("Objects to add:"));
    72         lblAdd.setLabelFor(lstAdd);
    73 
    74         lstUpdate = new PrimitiveList();
    75         lstUpdate.setCellRenderer(renderer);
    76         lstUpdate.addMouseListener(mouseListener);
    77         lstUpdate.setVisibleRowCount(Math.min(lstUpdate.getModel().getSize(), 10));
    78         spUpdate = new JScrollPane(lstUpdate);
    79         lblUpdate = new JLabel(tr("Objects to modify:"));
    80         lblUpdate.setLabelFor(lstUpdate);
    81 
    82         lstDelete = new PrimitiveList();
    83         lstDelete.setCellRenderer(renderer);
    84         lstDelete.addMouseListener(mouseListener);
    85         lstDelete.setVisibleRowCount(Math.min(lstDelete.getModel().getSize(), 10));
    86         spDelete = new JScrollPane(lstDelete);
    87         lblDelete = new JLabel(tr("Objects to delete:"));
    88         lblDelete.setLabelFor(lstDelete);
    89     }
    90 
    9178    /**
    92      * Sets the collections of primitives which will be uploaded
     79     * Builds the panel
    9380     *
    94      * @param add  the collection of primitives to add
    95      * @param update the collection of primitives to update
    96      * @param delete the collection of primitives to delete
     81     * @param toUpload the primitives to upload
    9782     */
    98     public void setUploadedPrimitives(List<OsmPrimitive> add, List<OsmPrimitive> update, List<OsmPrimitive> delete) {
    99         lstAdd.getPrimitiveListModel().setPrimitives(add);
    100         lstUpdate.getPrimitiveListModel().setPrimitives(update);
    101         lstDelete.getPrimitiveListModel().setPrimitives(delete);
    102 
    103         GridBagConstraints gcLabel = new GridBagConstraints();
    104         gcLabel.fill = GridBagConstraints.HORIZONTAL;
    105         gcLabel.weightx = 1.0;
    106         gcLabel.weighty = 0.0;
    107         gcLabel.anchor = GridBagConstraints.FIRST_LINE_START;
    108 
     83    public void build(APIDataSet toUpload) {
    10984        GridBagConstraints gcList = new GridBagConstraints();
    11085        gcList.fill = GridBagConstraints.BOTH;
    11186        gcList.weightx = 1.0;
    11287        gcList.weighty = 1.0;
    11388        gcList.anchor = GridBagConstraints.CENTER;
     89
    11490        removeAll();
    115         int y = -1;
    116         if (!add.isEmpty()) {
    117             y++;
    118             gcLabel.gridy = y;
    119             lblAdd.setText(trn("{0} object to add:", "{0} objects to add:", add.size(), add.size()));
    120             add(lblAdd, gcLabel);
    121             y++;
    122             gcList.gridy = y;
    123             add(spAdd, gcList);
     91        List<OsmPrimitive> list = toUpload.getPrimitivesToAdd();
     92        if (!list.isEmpty()) {
     93            gcList.gridy++;
     94            add(new ListPanel(list, "{0} object to add:", "{0} objects to add:"), gcList);
    12495        }
    125         if (!update.isEmpty()) {
    126             y++;
    127             gcLabel.gridy = y;
    128             lblUpdate.setText(trn("{0} object to modify:", "{0} objects to modify:", update.size(), update.size()));
    129             add(lblUpdate, gcLabel);
    130             y++;
    131             gcList.gridy = y;
    132             add(spUpdate, gcList);
     96        list = toUpload.getPrimitivesToUpdate();
     97        if (!list.isEmpty()) {
     98            gcList.gridy++;
     99            add(new ListPanel(list, "{0} object to modify:", "{0} objects to modify:"), gcList);
    133100        }
    134         if (!delete.isEmpty()) {
    135             y++;
    136             gcLabel.gridy = y;
    137             lblDelete.setText(trn("{0} object to delete:", "{0} objects to delete:", delete.size(), delete.size()));
    138             add(lblDelete, gcLabel);
    139             y++;
    140             gcList.gridy = y;
    141             add(spDelete, gcList);
     101        list = toUpload.getPrimitivesToDelete();
     102        if (!list.isEmpty()) {
     103            gcList.gridy++;
     104            add(new ListPanel(list, "{0} object to delete:", "{0} objects to delete:"), gcList);
    142105        }
    143106        revalidate();
     107        repaint();
    144108    }
    145 
    146     /**
    147      * Replies the number of objects to upload
    148      *
    149      * @return the number of objects to upload
    150      */
    151     public int getNumObjectsToUpload() {
    152         return lstAdd.getModel().getSize()
    153         + lstUpdate.getModel().getSize()
    154         + lstDelete.getModel().getSize();
    155     }
    156 
    157     /**
    158      * A simple list of OSM primitives.
    159      */
    160     static class PrimitiveList extends JList<OsmPrimitive> {
    161         /**
    162          * Constructs a new {@code PrimitiveList}.
    163          */
    164         PrimitiveList() {
    165             super(new PrimitiveListModel());
    166         }
    167 
    168         public PrimitiveListModel getPrimitiveListModel() {
    169             return (PrimitiveListModel) getModel();
    170         }
    171     }
    172 
    173     /**
    174      * A list model for a list of OSM primitives.
    175      */
    176     static class PrimitiveListModel extends AbstractListModel<OsmPrimitive> {
    177         private transient List<OsmPrimitive> primitives;
    178 
    179         /**
    180          * Constructs a new {@code PrimitiveListModel}.
    181          */
    182         PrimitiveListModel() {
    183             primitives = new ArrayList<>();
    184         }
    185 
    186         PrimitiveListModel(List<OsmPrimitive> primitives) {
    187             setPrimitives(primitives);
    188         }
    189 
    190         public void setPrimitives(List<OsmPrimitive> primitives) {
    191             this.primitives = Optional.ofNullable(primitives).orElseGet(ArrayList::new);
    192             fireContentsChanged(this, 0, getSize());
    193         }
    194 
    195         @Override
    196         public OsmPrimitive getElementAt(int index) {
    197             if (primitives == null) return null;
    198             return primitives.get(index);
    199         }
    200 
    201         @Override
    202         public int getSize() {
    203             if (primitives == null) return 0;
    204             return primitives.size();
    205         }
    206     }
    207109}
  • src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

     
    12801280    @Override
    12811281    public AbstractUploadDialog getUploadDialog() {
    12821282        UploadDialog dialog = UploadDialog.getUploadDialog();
    1283         dialog.setUploadedPrimitives(new APIDataSet(data));
     1283        dialog.initLifeCycle(data, new APIDataSet(data));
    12841284        return dialog;
    12851285    }
    12861286
  • src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java

     
    145145    }
    146146
    147147    @Override
    148     public Object getValueAt(int rowIndex, int columnIndex) {
    149         if (rowIndex >= getRowCount())
    150             throw new IndexOutOfBoundsException("unexpected rowIndex: rowIndex=" + rowIndex);
    151 
    152         return tags.get(rowIndex);
     148    public Object getValueAt(int row, int col) {
     149        if (row >= getRowCount())
     150            throw new IndexOutOfBoundsException("unexpected row: row=" + row);
     151        if (col >= getColumnCount())
     152            throw new IndexOutOfBoundsException("unexpected col: col=" + col);
     153        TagModel tag = get(row);
     154        switch(col) {
     155            case 0:
     156                return tag.getName();
     157            case 1:
     158                return tag.getValue();
     159            default: // Do nothing
     160        }
     161        return null;
    153162    }
    154163
    155164    @Override
  • src/org/openstreetmap/josm/gui/tagging/TagEditorPanel.java

     
    2121import org.openstreetmap.josm.gui.dialogs.properties.HelpAction;
    2222import org.openstreetmap.josm.gui.dialogs.properties.HelpTagAction;
    2323import org.openstreetmap.josm.gui.dialogs.properties.PresetListPanel;
    24 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    25 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
    26 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
    2724import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    2825import org.openstreetmap.josm.spi.preferences.Config;
    29 import org.openstreetmap.josm.tools.CheckParameterUtil;
    3026
    3127/**
    3228 * TagEditorPanel is a {@link JPanel} which can be embedded as UI component in
     
    9086        JButton btn = new JButton(action);
    9187        pnl.add(btn);
    9288        btn.setMargin(new Insets(0, 0, 0, 0));
    93         tagTable.addComponentNotStoppingCellEditing(btn);
    9489    }
    9590
    9691    /**
     
    147142     * @param presetHandler tagging preset handler
    148143     */
    149144    public TagEditorPanel(OsmPrimitive primitive, TaggingPresetHandler presetHandler) {
    150         this(new TagEditorModel().forPrimitive(primitive), presetHandler, 0);
     145        this(new TagEditorModel().forPrimitive(primitive), presetHandler, -1);
    151146    }
    152147
    153148    /**
     
    188183    }
    189184
    190185    /**
    191      * Initializes the auto completion infrastructure used in this
    192      * tag editor panel. {@code layer} is the data layer from whose data set
    193      * tag values are proposed as auto completion items.
    194      *
    195      * @param layer the data layer. Must not be null.
    196      * @throws IllegalArgumentException if {@code layer} is null
     186     * Returns the JTable
     187     * @return the JTable
    197188     */
    198     public void initAutoCompletion(OsmDataLayer layer) {
    199         CheckParameterUtil.ensureParameterNotNull(layer, "layer");
    200 
    201         AutoCompletionManager autocomplete = AutoCompletionManager.of(layer.data);
    202         AutoCompletionList acList = new AutoCompletionList();
    203 
    204         TagCellEditor editor = (TagCellEditor) tagTable.getColumnModel().getColumn(0).getCellEditor();
    205         editor.setAutoCompletionManager(autocomplete);
    206         editor.setAutoCompletionList(acList);
    207         editor = (TagCellEditor) tagTable.getColumnModel().getColumn(1).getCellEditor();
    208         editor.setAutoCompletionManager(autocomplete);
    209         editor.setAutoCompletionList(acList);
     189    public TagTable getTable() {
     190        return tagTable;
    210191    }
    211192
    212193    @Override
  • src/org/openstreetmap/josm/gui/tagging/TagTable.java

     
    55
    66import java.awt.Component;
    77import java.awt.Dimension;
    8 import java.awt.KeyboardFocusManager;
    9 import java.awt.Window;
    108import java.awt.event.ActionEvent;
    119import java.awt.event.KeyEvent;
    1210import java.beans.PropertyChangeEvent;
    1311import java.beans.PropertyChangeListener;
    1412import java.util.Collections;
    15 import java.util.EventObject;
    16 import java.util.concurrent.CopyOnWriteArrayList;
    1713
    1814import javax.swing.AbstractAction;
    1915import javax.swing.CellEditor;
     
    2117import javax.swing.JTable;
    2218import javax.swing.KeyStroke;
    2319import javax.swing.ListSelectionModel;
    24 import javax.swing.SwingUtilities;
    2520import javax.swing.event.ListSelectionEvent;
    2621import javax.swing.event.ListSelectionListener;
    27 import javax.swing.text.JTextComponent;
     22import javax.swing.table.DefaultTableCellRenderer;
     23import javax.swing.table.TableCellEditor;
    2824
    2925import org.openstreetmap.josm.data.osm.Relation;
    3026import org.openstreetmap.josm.data.osm.TagMap;
    3127import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
    3228import org.openstreetmap.josm.gui.tagging.TagEditorModel.EndEditListener;
    33 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
    34 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
    3529import org.openstreetmap.josm.gui.widgets.JosmTable;
    3630import org.openstreetmap.josm.tools.ImageProvider;
    37 import org.openstreetmap.josm.tools.Logging;
    3831import org.openstreetmap.josm.tools.Utils;
    3932
    4033/**
     
    4235 * @since 1762
    4336 */
    4437public class TagTable extends JosmTable implements EndEditListener {
    45     /** the table cell editor used by this table */
    46     private TagCellEditor editor;
    4738    private final TagEditorModel model;
    4839    private Component nextFocusComponent;
    4940
    50     /** a list of components to which focus can be transferred without stopping
    51      * cell editing this table.
    52      */
    53     private final CopyOnWriteArrayList<Component> doNotStopCellEditingWhenFocused = new CopyOnWriteArrayList<>();
    54     private transient CellEditorRemover editorRemover;
    55 
    5641    /**
    5742     * Action to be run when the user navigates to the next cell in the table,
    5843     * for instance by pressing TAB or ENTER. The action alters the standard
     
    8873                row++;
    8974            } else if (col == 1 && row == getRowCount()-1) {
    9075                // we are at the end. Append an empty row and move the focus to its second column
    91                 String key = ((TagModel) model.getValueAt(row, 0)).getName();
     76                String key = (String) model.getValueAt(row, 0);
    9277                if (!Utils.isStripEmpty(key)) {
    9378                    model.appendNewTag();
    9479                    col = 0;
     
    243228                cEditor.stopCellEditing();
    244229            }
    245230            final int rowIdx = model.getRowCount()-1;
    246             if (rowIdx < 0 || !Utils.isStripEmpty(((TagModel) model.getValueAt(rowIdx, 0)).getName())) {
     231            if (rowIdx < 0 || !Utils.isStripEmpty((String) model.getValueAt(rowIdx, 0))) {
    247232                model.appendNewTag();
    248233            }
    249234            requestFocusInCell(model.getRowCount()-1, 0);
     
    322307    }
    323308
    324309    /**
    325      * initialize the table
    326      * @param maxCharacters maximum number of characters allowed for keys and values, 0 for unlimited
     310     * Creates a new tag table
     311     *
     312     * @param model the tag editor model
     313     * @param maxCharacters maximum number of characters allowed for keys and values, -1 for unlimited
    327314     */
    328     protected final void init(final int maxCharacters) {
    329         setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
     315    public TagTable(TagEditorModel model, final int maxCharacters) {
     316        super(model, new TagTableColumnModelBuilder(new DefaultTableCellRenderer(), tr("Key"), tr("Value"))
     317                  .setSelectionModel(model.getColumnSelectionModel()).build(),
     318              model.getRowSelectionModel());
     319
     320        this.model = model;
     321        model.setEndEditListener(this);
     322
     323        setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
    330324        setRowSelectionAllowed(true);
    331325        setColumnSelectionAllowed(true);
    332326        setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
     327        putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
    333328
    334329        // make ENTER behave like TAB
    335330        //
     
    354349        getActionMap().put("addTag", addAction);
    355350
    356351        pasteAction = new PasteAction();
     352    }
    357353
    358         // create the table cell editor and set it to key and value columns
    359         //
    360         TagCellEditor tmpEditor = new TagCellEditor(maxCharacters);
    361         setRowHeight(tmpEditor.getEditor().getPreferredSize().height);
    362         setTagCellEditor(tmpEditor);
     354    /**
     355     * Sets a TableCellEditor for the keys column.
     356     * @param editor the editor to set
     357     */
     358    public void setKeyEditor(TableCellEditor editor) {
     359        getColumnModel().getColumn(0).setCellEditor(editor);
    363360    }
    364361
    365362    /**
    366      * Creates a new tag table
    367      *
    368      * @param model the tag editor model
    369      * @param maxCharacters maximum number of characters allowed for keys and values, 0 for unlimited
     363     * Sets a TableCellEditor for the values column.
     364     * @param editor the editor to set
    370365     */
    371     public TagTable(TagEditorModel model, final int maxCharacters) {
    372         super(model, new TagTableColumnModelBuilder(new TagCellRenderer(), tr("Key"), tr("Value"))
    373                   .setSelectionModel(model.getColumnSelectionModel()).build(),
    374               model.getRowSelectionModel());
    375         this.model = model;
    376         model.setEndEditListener(this);
    377         init(maxCharacters);
     366    public void setValueEditor(TableCellEditor editor) {
     367        getColumnModel().getColumn(1).setCellEditor(editor);
    378368    }
    379369
    380370    @Override
     371    public boolean getDragEnabled() {
     372        // fix for comboboxes flashing when clicking the cell where the arrow button will be
     373        // maybe a late focus request wants to focus the cb when the popup is already open?
     374        // see: BasicTableUI#adjustSelection and mouseReleasedDND
     375        return true;
     376    }
     377
     378    @Override
    381379    public Dimension getPreferredSize() {
    382380        return getPreferredFullWidthSize();
    383381    }
     
    400398    }
    401399
    402400    /**
    403      * Sets the editor autocompletion list
    404      * @param autoCompletionList autocompletion list
    405      */
    406     public void setAutoCompletionList(AutoCompletionList autoCompletionList) {
    407         if (autoCompletionList == null)
    408             return;
    409         if (editor != null) {
    410             editor.setAutoCompletionList(autoCompletionList);
    411         }
    412     }
    413 
    414     /**
    415      * Sets the autocompletion manager that should be used for editing the cells
    416      * @param autocomplete The {@link AutoCompletionManager}
    417      */
    418     public void setAutoCompletionManager(AutoCompletionManager autocomplete) {
    419         if (autocomplete == null) {
    420             Logging.warn("argument autocomplete should not be null. Aborting.");
    421             Logging.error(new Exception());
    422             return;
    423         }
    424         if (editor != null) {
    425             editor.setAutoCompletionManager(autocomplete);
    426         }
    427     }
    428 
    429     /**
    430      * Gets the {@link AutoCompletionList} the cell editor is synchronized with
    431      * @return The list
    432      */
    433     public AutoCompletionList getAutoCompletionList() {
    434         if (editor != null)
    435             return editor.getAutoCompletionList();
    436         else
    437             return null;
    438     }
    439 
    440     /**
    441401     * Sets the next component to request focus after navigation (with tab or enter).
    442402     * @param nextFocusComponent next component to request focus after navigation (with tab or enter)
    443403     */
     
    446406    }
    447407
    448408    /**
    449      * Gets the editor that is used for the table cells
    450      * @return The editor that is used when the user wants to enter text into a cell
    451      */
    452     public TagCellEditor getTableCellEditor() {
    453         return editor;
    454     }
    455 
    456     /**
    457      * Inject a tag cell editor in the tag table
    458      *
    459      * @param editor tag cell editor
    460      */
    461     public void setTagCellEditor(TagCellEditor editor) {
    462         endCellEditing();
    463         this.editor = editor;
    464         getColumnModel().getColumn(0).setCellEditor(editor);
    465         getColumnModel().getColumn(1).setCellEditor(editor);
    466     }
    467 
    468     /**
    469409     * Request the focus in a specific cell
    470410     * @param row The row index
    471411     * @param col The column index
     
    475415        editCellAt(row, col);
    476416        Component c = getEditorComponent();
    477417        if (c != null) {
    478             if (!c.requestFocusInWindow()) {
    479                 Logging.warn("Unable to request focus for " + c);
    480             }
    481             if (c instanceof JTextComponent) {
    482                  ((JTextComponent) c).selectAll();
    483             }
     418            c.requestFocusInWindow();
    484419        }
    485         // there was a bug here - on older 1.6 Java versions Tab was not working
    486         // after such activation. In 1.7 it works OK,
    487         // previous solution of using awt.Robot was resetting mouse speed on Windows
    488420    }
    489421
    490     /**
    491      * Marks a component that may be focused without stopping the cell editing
    492      * @param component The component
    493      */
    494     public void addComponentNotStoppingCellEditing(Component component) {
    495         if (component == null) return;
    496         doNotStopCellEditingWhenFocused.addIfAbsent(component);
    497     }
    498 
    499     /**
    500      * Removes a component added with {@link #addComponentNotStoppingCellEditing(Component)}
    501      * @param component The component
    502      */
    503     public void removeComponentNotStoppingCellEditing(Component component) {
    504         if (component == null) return;
    505         doNotStopCellEditingWhenFocused.remove(component);
    506     }
    507 
    508422    @Override
    509     public boolean editCellAt(int row, int column, EventObject e) {
    510 
    511         // a snipped copied from the Java 1.5 implementation of JTable
    512         //
    513         if (cellEditor != null && !cellEditor.stopCellEditing())
    514             return false;
    515 
    516         if (row < 0 || row >= getRowCount() ||
    517                 column < 0 || column >= getColumnCount())
    518             return false;
    519 
    520         if (!isCellEditable(row, column))
    521             return false;
    522 
    523         // make sure our custom implementation of CellEditorRemover is created
    524         if (editorRemover == null) {
    525             KeyboardFocusManager fm =
    526                 KeyboardFocusManager.getCurrentKeyboardFocusManager();
    527             editorRemover = new CellEditorRemover(fm);
    528             fm.addPropertyChangeListener("permanentFocusOwner", editorRemover);
    529         }
    530 
    531         // delegate to the default implementation
    532         return super.editCellAt(row, column, e);
    533     }
    534 
    535     @Override
    536423    public void endCellEditing() {
    537         if (isEditing()) {
    538             CellEditor cEditor = getCellEditor();
    539             if (cEditor != null) {
    540                 // First attempt to commit. If this does not work, cancel.
    541                 cEditor.stopCellEditing();
     424        TableCellEditor cEditor = getCellEditor();
     425        if (cEditor != null) {
     426            // First attempt to commit. If this does not work, cancel.
     427            if (!cEditor.stopCellEditing()) {
    542428                cEditor.cancelCellEditing();
    543429            }
    544430        }
    545431    }
    546432
    547     @Override
    548     public void removeEditor() {
    549         // make sure we unregister our custom implementation of CellEditorRemover
    550         KeyboardFocusManager.getCurrentKeyboardFocusManager().
    551         removePropertyChangeListener("permanentFocusOwner", editorRemover);
    552         editorRemover = null;
    553         super.removeEditor();
    554     }
    555 
    556     @Override
    557     public void removeNotify() {
    558         // make sure we unregister our custom implementation of CellEditorRemover
    559         KeyboardFocusManager.getCurrentKeyboardFocusManager().
    560         removePropertyChangeListener("permanentFocusOwner", editorRemover);
    561         editorRemover = null;
    562         super.removeNotify();
    563     }
    564 
    565     /**
    566      * This is a custom implementation of the CellEditorRemover used in JTable
    567      * to handle the client property <code>terminateEditOnFocusLost</code>.
    568      *
    569      * This implementation also checks whether focus is transferred to one of a list
    570      * of dedicated components, see {@link TagTable#doNotStopCellEditingWhenFocused}.
    571      * A typical example for such a component is a button in {@link TagEditorPanel}
    572      * which isn't a child component of {@link TagTable} but which should respond to
    573      * to focus transfer in a similar way to a child of TagTable.
    574      *
    575      */
    576     class CellEditorRemover implements PropertyChangeListener {
    577         private final KeyboardFocusManager focusManager;
    578 
    579         CellEditorRemover(KeyboardFocusManager fm) {
    580             this.focusManager = fm;
    581         }
    582 
    583         @Override
    584         public void propertyChange(PropertyChangeEvent ev) {
    585             if (!isEditing())
    586                 return;
    587 
    588             Component c = focusManager.getPermanentFocusOwner();
    589             while (c != null) {
    590                 if (c == TagTable.this)
    591                     // focus remains inside the table
    592                     return;
    593                 if (doNotStopCellEditingWhenFocused.contains(c))
    594                     // focus remains on one of the associated components
    595                     return;
    596                 else if (c instanceof Window) {
    597                     if (c == SwingUtilities.getRoot(TagTable.this) && !getCellEditor().stopCellEditing()) {
    598                         getCellEditor().cancelCellEditing();
    599                     }
    600                     break;
    601                 }
    602                 c = c.getParent();
    603             }
    604         }
    605     }
    606433}
  • src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.tagging.ac;
    33
     4import java.awt.Component;
     5import java.awt.event.MouseEvent;
    46import java.awt.im.InputContext;
    57import java.util.Collection;
    68import java.util.Collections;
     9import java.util.EventObject;
    710import java.util.LinkedList;
    811import java.util.Locale;
    912
    1013import javax.swing.ComboBoxEditor;
     14import javax.swing.JTable;
     15import javax.swing.event.CellEditorListener;
     16import javax.swing.table.TableCellEditor;
    1117
     18import org.openstreetmap.josm.gui.util.CellEditorSupport;
    1219import org.openstreetmap.josm.gui.widgets.JosmComboBox;
    1320import org.openstreetmap.josm.tools.Logging;
    1421
     
    2431 * @param <E> the type of the combobox entries
    2532 * @since 18173
    2633 */
    27 public class AutoCompComboBox<E> extends JosmComboBox<E> implements AutoCompListener {
     34public class AutoCompComboBox<E> extends JosmComboBox<E> implements TableCellEditor, AutoCompListener {
    2835
    2936    /** force a different keyboard input locale for the editor */
    3037    private boolean useFixedLocale;
     
    4855        setEditable(true);
    4956        getEditorComponent().setModel(model);
    5057        getEditorComponent().addAutoCompListener(this);
     58        tableCellEditorSupport = new CellEditorSupport(this);
    5159    }
    5260
    5361    /**
     
    104112     * @param elems The string items to set
    105113     * @deprecated Has been moved to the model, where it belongs. Use
    106114     *     {@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}.
     115     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#load} and
     116     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#save}.
    109117     */
    110118    @Deprecated
    111119    public void setPossibleItems(Collection<E> elems) {
     
    122130     * @since 15011
    123131     * @deprecated Has been moved to the model, where it belongs. Use
    124132     *     {@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}.
     133     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#load} and
     134     *     {@link org.openstreetmap.josm.gui.widgets.JosmComboBoxModel.Preferences#save}.
    127135     */
    128136    @Deprecated
    129137    public void setPossibleItemsTopDown(Collection<E> elems) {
     
    201209    public void autoCompPerformed(AutoCompEvent e) {
    202210        autocomplete(e.getItem());
    203211    }
     212
     213    /* ------------------------------------------------------------------------------------ */
     214    /* TableCellEditor interface                                                            */
     215    /* ------------------------------------------------------------------------------------ */
     216
     217    private transient CellEditorSupport tableCellEditorSupport;
     218    private String originalValue;
     219
     220    @Override
     221    public void addCellEditorListener(CellEditorListener l) {
     222        tableCellEditorSupport.addCellEditorListener(l);
     223    }
     224
     225    protected void rememberOriginalValue(String value) {
     226        this.originalValue = value;
     227    }
     228
     229    protected void restoreOriginalValue() {
     230        setText(originalValue);
     231    }
     232
     233    @Override
     234    public void removeCellEditorListener(CellEditorListener l) {
     235        tableCellEditorSupport.removeCellEditorListener(l);
     236    }
     237
     238    @Override
     239    public void cancelCellEditing() {
     240        restoreOriginalValue();
     241        tableCellEditorSupport.fireEditingCanceled();
     242    }
     243
     244    @Override
     245    public Object getCellEditorValue() {
     246        return getText();
     247    }
     248
     249    /**
     250    * Returns true if <code>anEvent</code> is <b>not</b> a <code>MouseEvent</code>.  Otherwise, it
     251    * returns true if the necessary number of clicks have occurred, and returns false otherwise.
     252    *
     253    * @param   anEvent         the event
     254    * @return  true  if cell is ready for editing, false otherwise
     255    * @see #shouldSelectCell
     256    */
     257    @Override
     258    public boolean isCellEditable(EventObject anEvent) {
     259        if (anEvent instanceof MouseEvent) {
     260            return ((MouseEvent) anEvent).getClickCount() >= 1;
     261        }
     262        return true;
     263    }
     264
     265    @Override
     266    public boolean shouldSelectCell(EventObject anEvent) {
     267        if (anEvent instanceof MouseEvent) {
     268            MouseEvent e = (MouseEvent) anEvent;
     269            return e.getID() != MouseEvent.MOUSE_DRAGGED;
     270        }
     271        return true;
     272    }
     273
     274    @Override
     275    public boolean stopCellEditing() {
     276        tableCellEditorSupport.fireEditingStopped();
     277        return true;
     278    }
     279
     280    @Override
     281    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
     282        setText(value == null ? "" : value.toString());
     283        rememberOriginalValue(getText());
     284        return this;
     285    }
    204286}
  • 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;
     
    1616import java.util.Set;
    1717import java.util.function.Function;
    1818import java.util.stream.Collectors;
     19import java.util.stream.Stream;
    1920
    2021import org.openstreetmap.josm.data.osm.DataSet;
    2122import org.openstreetmap.josm.data.osm.OsmPrimitive;
     
    4041import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
    4142import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    4243import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
     44import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
     45import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
    4346import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
     47import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
    4448import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
    4549import org.openstreetmap.josm.tools.CheckParameterUtil;
    4650import org.openstreetmap.josm.tools.MultiMap;
     
    4751import org.openstreetmap.josm.tools.Utils;
    4852
    4953/**
    50  * AutoCompletionManager holds a cache of keys with a list of
    51  * possible auto completion values for each key.
    52  *
     54 * AutoCompletionManager holds a cache of keys with a list of possible auto completion values for
     55 * each key.
     56 * <p>
    5357 * Each DataSet can be assigned one AutoCompletionManager instance such that
    5458 * <ol>
    5559 *   <li>any key used in a tag in the data set is part of the key list in the cache</li>
     
    5660 *   <li>any value used in a tag for a specific key is part of the autocompletion list of this key</li>
    5761 * </ol>
    5862 *
    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
     63 * Building up auto completion lists should not slow down tabbing from input field to input field.
     64 * Looping through the complete data set in order to build up the auto completion list for a
     65 * specific input field is not efficient enough, hence this cache.
    6566 */
    6667public class AutoCompletionManager implements DataSetListener {
    6768
     
    105106        }
    106107    }
    107108
     109    /**
     110     * Compares two AutoCompletionItems alphabetically.
     111     */
     112    public static final Comparator<AutoCompletionItem> ALPHABETIC_COMPARATOR =
     113        (ac1, ac2) -> String.CASE_INSENSITIVE_ORDER.compare(ac1.getValue(), ac2.getValue());
     114
    108115    /** If the dirty flag is set true, a rebuild is necessary. */
    109116    protected boolean dirty;
    110117    /** The data set that is managed */
     
    115122     * only accessed by getTagCache(), rebuild() and cachePrimitiveTags()
    116123     * use getTagCache() accessor
    117124     */
    118     protected MultiMap<String, String> tagCache;
     125    protected final MultiMap<String, String> TAG_CACHE = new MultiMap<>();
    119126
    120127    /**
    121      * the same as tagCache but for the preset keys and values can be accessed directly
    122      */
    123     static final MultiMap<String, String> PRESET_TAG_CACHE = new MultiMap<>();
    124 
    125     /**
    126128     * Cache for tags that have been entered by the user.
    127129     */
    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 relations by {@link #getRelationType(Map) relation type}.
    134134     */
    135     protected Set<String> roleCache;
     135    protected final MultiMap<String, Relation> RELATION_CACHE = new MultiMap<>();
    136136
    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 
    142137    private static final Map<DataSet, AutoCompletionManager> INSTANCES = new HashMap<>();
    143138
     139    private static final Map<String, String> EMPTY_MAP = Collections.emptyMap();
     140
    144141    /**
    145142     * Constructs a new {@code AutoCompletionManager}.
    146143     * @param ds data set
     
    156153            rebuild();
    157154            dirty = false;
    158155        }
    159         return tagCache;
     156        return TAG_CACHE;
    160157    }
    161158
    162     protected Set<String> getRoleCache() {
     159    protected MultiMap<String, Relation> getRelationCache() {
    163160        if (dirty) {
    164161            rebuild();
    165162            dirty = false;
    166163        }
    167         return roleCache;
     164        return RELATION_CACHE;
    168165    }
    169166
    170167    /**
     
    171168     * initializes the cache from the primitives in the dataset
    172169     */
    173170    protected void rebuild() {
    174         tagCache = new MultiMap<>();
    175         roleCache = new HashSet<>();
     171        TAG_CACHE.clear();
     172        RELATION_CACHE.clear();
    176173        cachePrimitives(ds.allNonDeletedCompletePrimitives());
    177174    }
    178175
     
    180177        for (OsmPrimitive primitive : primitives) {
    181178            cachePrimitiveTags(primitive);
    182179            if (primitive instanceof Relation) {
    183                 cacheRelationMemberRoles((Relation) primitive);
     180                Relation rel = (Relation) primitive;
     181                RELATION_CACHE.put(getRelationType(rel.getKeys()), rel);
    184182            }
    185183        }
    186184    }
     
    192190     * @param primitive an OSM primitive
    193191     */
    194192    protected void cachePrimitiveTags(OsmPrimitive primitive) {
    195         primitive.visitKeys((p, key, value) -> tagCache.put(key, value));
     193        primitive.visitKeys((p, key, value) -> TAG_CACHE.put(key, value));
    196194    }
    197195
    198196    /**
    199      * Caches all member roles of the relation <code>relation</code>
     197     * Returns the relation type.
     198     * <p>
     199     * This is used to categorize the relations in the dataset.  A relation with the keys:
     200     * <ul>
     201     * <li>type=route
     202     * <li>route=hiking
     203     * </ul>
     204     * will return a relation type of {@code "route.hiking"}.
    200205     *
    201      * @param relation the relation
     206     * @param tags the tags on the relation
     207     * @return the relation type or {@code ""}
    202208     */
    203     protected void cacheRelationMemberRoles(Relation relation) {
    204         for (RelationMember m: relation.getMembers()) {
    205             if (m.hasRole()) {
    206                 roleCache.add(m.getRole());
    207             }
    208         }
     209    private String getRelationType(Map<String, String> tags) {
     210        String type = tags.get("type");
     211        if (type == null) return "";
     212        String subtype = tags.get(type);
     213        if (subtype == null) return type;
     214        return type + "." + subtype;
    209215    }
    210216
    211217    /**
     218     * Construct a role out of a relation member
     219     *
     220     * @param member the relation member
     221     * @return the Role
     222     */
     223    protected Role mkRole(RelationMember member) {
     224        Role role = new Role();
     225        role.key = member.getRole();
     226        role.types = EnumSet.of(TaggingPresetType.forPrimitiveType(member.getDisplayType()));
     227        return role;
     228    }
     229
     230    /**
    212231     * Remembers user input for the given key/value.
    213232     * @param key Tag key
    214233     * @param value Tag value
     
    259278    }
    260279
    261280    /**
    262      * Replies the list of member roles
     281     * Returns a collection of all member roles in the dataset.
     282     * <p>
     283     * Member roles are distinct on role name and primitive type they apply to. So there will be a
     284     * role "platform" for nodes and a role "platform" for ways.
    263285     *
    264      * @return the list of member roles
     286     * @return the collection of member roles
    265287     */
    266     public List<String> getMemberRoles() {
    267         return new ArrayList<>(getRoleCache());
     288    public Set<Role> getAllMemberRoles() {
     289        return getRelationCache().getAllValues().stream()
     290            .flatMap(rel -> rel.getMembers().stream()).map(r -> mkRole(r)).collect(Collectors.toSet());
    268291    }
    269292
    270293    /**
     294     * Returns a collection of all roles in the dataset for one relation type.
     295     * <p>
     296     * Member roles are distinct on role name and primitive type they apply to. So there will be a
     297     * role "platform" for nodes and a role "platform" for ways.
     298     *
     299     * @param type the {@link #getRelationType(Map) relation type}
     300     * @return the collection of member roles
     301     */
     302    public Set<Role> getMemberRoles(String type) {
     303        Set<Relation> relations = getRelationCache().get(type);
     304        if (relations == null)
     305            return Collections.emptySet();
     306        return relations.stream().flatMap(rel -> rel.getMembers().stream()).map(r -> mkRole(r)).collect(Collectors.toSet());
     307    }
     308
     309    /**
    271310     * Populates the {@link AutoCompletionList} with the currently cached member roles.
    272311     *
    273312     * @param list the list to populate
    274313     */
    275314    public void populateWithMemberRoles(AutoCompletionList list) {
    276         list.add(TaggingPresets.getPresetRoles(), AutoCompletionPriority.IS_IN_STANDARD);
    277         list.add(getRoleCache(), AutoCompletionPriority.IS_IN_DATASET);
     315        list.add(TaggingPresets.getPresetRoles().stream().map(r -> r.key).collect(Collectors.toList()), AutoCompletionPriority.IS_IN_STANDARD);
     316        list.add(getAllMemberRoles().stream().map(role -> role.key).collect(Collectors.toSet()), AutoCompletionPriority.IS_IN_DATASET);
    278317    }
    279318
    280319    /**
     
    303342    }
    304343
    305344    /**
     345     * Merges two or more {@code Map<String, AutoCompletionPriority>}. The result will have the
     346     * priorities merged.
     347     *
     348     * @param maps two or more maps to merge
     349     * @return the merged map
     350     */
     351    @SafeVarargs
     352    public static final Map<String, AutoCompletionPriority> merge(Map<String, AutoCompletionPriority>... maps) {
     353        return Stream.of(maps).flatMap(m -> m.entrySet().stream())
     354            .collect(Collectors.toMap(Entry::getKey, Entry::getValue, AutoCompletionPriority::mergeWith));
     355    }
     356
     357    /**
     358     * Return true if the role may be applied to all of the types.
     359     * <p>
     360     * Returns true if {@code role.types} contains all elements of {@code types}.
     361     *
     362     * @param role The role.
     363     * @param types The types.
     364     * @return True if the role may be applied to all of the types.
     365     */
     366    private boolean appliesTo(Role role, Collection<TaggingPresetType> types) {
     367        return role.types.containsAll(types);
     368    }
     369
     370    /**
     371     * Returns key suggestions for a given relation type.
     372     * <p>
     373     * Returns all keys in the dataset used on a given {@link #getRelationType(Map) relation type}.
     374     *
     375     * @param tags current tags in the tag editor panel, used to determine the relation type
     376     * @return the suggestions
     377     */
     378    public Map<String, AutoCompletionPriority> getKeysForRelation(Map<String, String> tags) {
     379        Map<String, AutoCompletionPriority> map = new HashMap<>();
     380        Set<Relation> relations = tags != null ? getRelationCache().get(getRelationType(tags)) : getRelationCache().getAllValues();
     381        if (relations == null)
     382            return map;
     383        return relations.stream().flatMap(rel -> rel.getKeys().entrySet().stream()).map(e -> e.getKey())
     384            .collect(Collectors.toMap(k -> k, v -> AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith));
     385    }
     386
     387    /**
     388     * Returns value suggestions for a given relation type and key.
     389     * <p>
     390     * Returns all values in the dataset used with a given key on a given
     391     * {@link #getRelationType(Map) relation type}.
     392     *
     393     * @param tags current tags in the tag editor panel, used to determine the relation type
     394     * @param key the key to get values for
     395     * @return the suggestions
     396     */
     397    public Map<String, AutoCompletionPriority> getValuesForRelation(Map<String, String> tags, String key) {
     398        Map<String, AutoCompletionPriority> map = new HashMap<>();
     399        Set<Relation> relations = tags != null ? getRelationCache().get(getRelationType(tags)) : getRelationCache().getAllValues();
     400        if (relations == null)
     401            return map;
     402        return relations.stream().map(rel -> rel.get(key)).filter(e -> e != null)
     403            .collect(Collectors.toMap(k -> k, v -> AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith));
     404    }
     405
     406    /**
     407     * Returns role suggestions for a given relation type.
     408     * <p>
     409     * Returns all roles in the dataset for a given {@link TaggingPresetType role type} used with a given
     410     * {@link #getRelationType(Map) relation type}.
     411     *
     412     * @param tags current tags in the tag editor panel, used to determine the relation type
     413     * @param roleTypes all roles returned will match all of the types in this set.
     414     * @return the suggestions
     415     */
     416    public Map<String, AutoCompletionPriority> getRolesForRelation(Map<String, String> tags, EnumSet<TaggingPresetType> roleTypes) {
     417        Map<String, AutoCompletionPriority> map = new HashMap<>();
     418        Set<Relation> relations = tags != null ? getRelationCache().get(getRelationType(tags)) : getRelationCache().getAllValues();
     419        if (relations == null)
     420            return map;
     421        return relations.stream().flatMap(rel -> rel.getMembers().stream())
     422            .map(member -> mkRole(member)).filter(role -> appliesTo(role, roleTypes))
     423            .collect(Collectors.toMap(k -> k.key, v -> AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith));
     424    }
     425
     426    /**
     427     * Returns all presets of type {@code types} matched by {@code tags}.
     428     *
     429     * @param types the preset types to include, (node / way / relation ...) or null to include all types
     430     * @param tags match presets using these tags or null to match all presets
     431     * @return the matched presets
     432     */
     433    private Collection<TaggingPreset> getPresets(Collection<TaggingPresetType> types, Map<String, String> tags) {
     434        if (tags == null)
     435            tags = EMPTY_MAP;
     436
     437        Collection<TaggingPreset> presets = TaggingPresets.getMatchingPresets(types, tags, false);
     438        if (presets.isEmpty()) {
     439            presets = TaggingPresets.getTaggingPresets();
     440        }
     441        return presets;
     442    }
     443
     444    /**
     445     * Returns all keys found in the presets matched by {@code tags}.
     446     *
     447     * @param types the preset types to include, (node / way / relation ...) or null to include all types
     448     * @param tags match presets using these tags or null to match all presets
     449     * @return the suggested keys
     450     * @since xxx
     451     */
     452    public Map<String, AutoCompletionPriority> getPresetKeys(Collection<TaggingPresetType> types, Map<String, String> tags) {
     453        Map<String, AutoCompletionPriority> map = new HashMap<>();
     454
     455        for (TaggingPreset preset : getPresets(types, tags)) {
     456            for (TaggingPresetItem item : preset.data) {
     457                if (item instanceof KeyedItem) {
     458                    map.merge(((KeyedItem) item).key, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
     459                }
     460            }
     461        }
     462        return map;
     463    }
     464
     465    /**
     466     * Returns all values for {@code key} found in the presets matched by {@code tags}.
     467     *
     468     * @param types the preset types to include, (node / way / relation ...) or null to include all types
     469     * @param tags match presets using these tags or null to match all presets
     470     * @param key the key to return values for
     471     * @return the suggested values
     472     * @since xxx
     473     */
     474    public Map<String, AutoCompletionPriority> getPresetValues(Collection<TaggingPresetType> types, Map<String, String> tags, String key) {
     475        Map<String, AutoCompletionPriority> map = new HashMap<>();
     476
     477        for (TaggingPreset preset : getPresets(types, tags)) {
     478            for (TaggingPresetItem item : preset.data) {
     479                if (item instanceof KeyedItem) {
     480                    KeyedItem keyedItem = (KeyedItem) item;
     481                    if (keyedItem.key.equals(key)) {
     482                        for (String value : keyedItem.getValues()) {
     483                            map.merge(value, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
     484                        }
     485                    }
     486                }
     487            }
     488        }
     489        return map;
     490    }
     491
     492    /**
     493     * Returns all roles found in the presets matched by {@code tags}.
     494     *
     495     * @param tags match presets using these tags or null to match all presets
     496     * @param roleTypes the role types to include, (node / way / relation ...) or null to include all types
     497     * @return the suggested roles
     498     * @since xxx
     499     */
     500    public Map<String, AutoCompletionPriority> getPresetRoles(Map<String, String> tags, Collection<TaggingPresetType> roleTypes) {
     501        Map<String, AutoCompletionPriority> map = new HashMap<>();
     502
     503        for (TaggingPreset preset : getPresets(EnumSet.of(TaggingPresetType.RELATION), tags)) {
     504            if (preset.roles != null) {
     505                for (Role role : preset.roles.roles) {
     506                    if (appliesTo(role, roleTypes))
     507                        map.merge(role.key, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
     508                }
     509            }
     510        }
     511        return map;
     512    }
     513
     514    /**
     515     * Returns all cached {@link AutoCompletionItem}s for given keys.
     516     *
     517     * @param keys retrieve the items for these keys
     518     * @return the currently cached items, sorted by priority and alphabet
     519     * @since 18221
     520     */
     521    public List<AutoCompletionItem> getAllValuesForKeys(List<String> keys) {
     522        Map<String, AutoCompletionPriority> map = new HashMap<>();
     523
     524        for (String key : keys) {
     525            for (String value : TaggingPresets.getPresetValues(key)) {
     526                map.merge(value, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
     527            }
     528            for (String value : getDataValues(key)) {
     529                map.merge(value, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith);
     530            }
     531            for (String value : getUserInputValues(key)) {
     532                map.merge(value, AutoCompletionPriority.UNKNOWN, AutoCompletionPriority::mergeWith);
     533            }
     534        }
     535        return map.entrySet().stream().map(e -> new AutoCompletionItem(e.getKey(), e.getValue()))
     536            .sorted(ALPHABETIC_COMPARATOR).collect(Collectors.toList());
     537    }
     538
     539    /**
    306540     * Populates the an {@link AutoCompletionList} with the currently cached tag keys
    307541     *
    308542     * @param list the list to populate
     
    345579    }
    346580
    347581    /**
    348      * Returns all cached {@link AutoCompletionItem}s for given keys.
    349      *
    350      * @param keys retrieve the items for these keys
    351      * @return the currently cached items, sorted by priority and alphabet
    352      * @since 18221
    353      */
    354     public List<AutoCompletionItem> getAllForKeys(List<String> keys) {
    355         Map<String, AutoCompletionPriority> map = new HashMap<>();
    356 
    357         for (String key : keys) {
    358             for (String value : TaggingPresets.getPresetValues(key)) {
    359                 map.merge(value, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith);
    360             }
    361             for (String value : getDataValues(key)) {
    362                 map.merge(value, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith);
    363             }
    364             for (String value : getUserInputValues(key)) {
    365                 map.merge(value, AutoCompletionPriority.UNKNOWN, AutoCompletionPriority::mergeWith);
    366             }
    367         }
    368         return map.entrySet().stream().map(e -> new AutoCompletionItem(e.getKey(), e.getValue())).sorted().collect(Collectors.toList());
    369     }
    370 
    371     /**
    372582     * Returns the currently cached tag keys.
    373583     * @return a set of tag keys
    374584     * @since 12859
     
    502712                    ds.removeDataSetListener(AutoCompletionManager.this);
    503713                    MainApplication.getLayerManager().removeLayerChangeListener(this);
    504714                    dirty = true;
    505                     tagCache = null;
    506                     roleCache = null;
     715                    TAG_CACHE.clear();
     716                    RELATION_CACHE.clear();
    507717                    ds = null;
    508718                }
    509719            }
  • src/org/openstreetmap/josm/gui/tagging/ac/DefaultAutoCompListener.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.tagging.ac;
     3
     4import javax.swing.event.PopupMenuEvent;
     5import javax.swing.event.PopupMenuListener;
     6
     7/**
     8 * A default autocompletion listener.
     9 * @param <E> the type of the {@code AutoCompComboBox<E>} or {@code AutoCompTextField<E>}
     10 */
     11public class DefaultAutoCompListener<E> implements AutoCompListener, PopupMenuListener {
     12    protected void updateAutoCompModel(AutoCompComboBoxModel<E> model) {
     13    }
     14
     15    @Override
     16    public void autoCompBefore(AutoCompEvent e) {
     17        AutoCompTextField<E> tf = toTextField(e);
     18        String savedText = tf.getText();
     19        updateAutoCompModel(tf.getModel());
     20        tf.setText(savedText);
     21    }
     22
     23    @Override
     24    public void autoCompPerformed(AutoCompEvent e) {
     25    }
     26
     27    @Override
     28    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
     29        AutoCompComboBox<E> cb = toComboBox(e);
     30        String savedText = cb.getText();
     31        updateAutoCompModel(cb.getModel());
     32        cb.setText(savedText);
     33    }
     34
     35    @Override
     36    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
     37    }
     38
     39    @Override
     40    public void popupMenuCanceled(PopupMenuEvent e) {
     41    }
     42
     43    /**
     44     * Returns the AutoCompTextField that sent the request.
     45     * @param e The AutoCompEvent
     46     * @return the AutoCompTextField
     47     */
     48    @SuppressWarnings("unchecked")
     49    public AutoCompTextField<E> toTextField(AutoCompEvent e) {
     50        return (AutoCompTextField<E>) e.getSource();
     51    }
     52
     53    /**
     54     * Returns the AutoCompComboBox that sent the request.
     55     * @param e The AutoCompEvent
     56     * @return the AutoCompComboBox
     57     */
     58    @SuppressWarnings("unchecked")
     59    public AutoCompComboBox<E> toComboBox(PopupMenuEvent e) {
     60        return (AutoCompComboBox<E>) e.getSource();
     61    }
     62}
  • 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     *
     
    7053        if (data == null) {
    7154            return Collections.emptyList();
    7255        }
    73         return AutoCompletionManager.of(data).getAllForKeys(keys);
     56        return AutoCompletionManager.of(data).getAllValuesForKeys(keys);
    7457    }
    7558
    7659    /**
  • 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/gui/widgets/JosmComboBox.java

     
    5959    /** greyed text to display in the editor when the selected value is empty */
    6060    private String hint;
    6161
     62    private boolean fakeWidth;
     63
    6264    /**
    6365     * Creates a {@code JosmComboBox} with a {@link JosmComboBoxModel} data model.
    6466     * The default data model is an empty list of objects.
     
    293295    }
    294296
    295297    /**
     298     * Make popup wider than combobox.
     299     */
     300    @Override
     301    public Dimension getSize() {
     302        Dimension dim = super.getSize();
     303        if (fakeWidth)
     304            dim.width = Math.max(getPreferredSize().width, dim.width);
     305        return dim;
     306    }
     307
     308    /**
     309     * Helper to make popup wider than combobox.
     310     */
     311    @Override
     312    public void doLayout() {
     313        try {
     314            fakeWidth = false;
     315            super.doLayout();
     316        } finally {
     317            fakeWidth = true;
     318        }
     319    }
     320
     321    /**
    296322     * Get the dropdown list component
    297323     *
    298324     * @return the list or null
     
    383409        int freeAbove = bounds.y - screenBounds.y;
    384410        int freeBelow = (screenBounds.y + screenBounds.height) - (bounds.y + bounds.height);
    385411
    386         try {
    387             // First try an implementation-dependent method to get the exact number.
    388             @SuppressWarnings("unchecked")
    389             JList<E> jList = getList();
    390 
     412        int maxRowCount = 8; // default
     413        @SuppressWarnings("unchecked")
     414        JList<E> jList = getList();
     415        if (jList != null) {
    391416            // Calculate the free space available on screen
    392417            Insets insets = jList.getInsets();
    393418            // A small fudge factor that accounts for the displacement of the popup relative to the
     
    394419            // combobox and the popup shadow.
    395420            int fudge = 4;
    396421            int free = Math.max(freeAbove, freeBelow) - (insets.top + insets.bottom) - fudge;
    397             if (jList.getParent() instanceof JScrollPane) {
    398                 JScrollPane scroller = (JScrollPane) jList.getParent();
     422            Component parent = getParent();
     423            if (parent instanceof JScrollPane) {
     424                JScrollPane scroller = (JScrollPane) parent;
    399425                Border border = scroller.getViewportBorder();
    400426                if (border != null) {
    401427                    insets = border.getBorderInsets(null);
     
    418444                if (h >= free)
    419445                    break;
    420446            }
    421             setMaximumRowCount(i);
    422             // Logging.debug("free = {0}, h = {1}, i = {2}, bounds = {3}, screenBounds = {4}", free, h, i, bounds, screenBounds);
    423         } catch (Exception ex) {
    424             setMaximumRowCount(8); // the default
     447            maxRowCount = i;
    425448        }
     449        // Logging.debug("free = {0}, h = {1}, i = {2}, bounds = {3}, screenBounds = {4}", free, h, i, bounds, screenBounds);
     450        setMaximumRowCount(maxRowCount);
    426451    }
    427452
    428453    @Override
  • src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java

     
    205205    }
    206206
    207207    /**
     208     * Replaces all current elements with elements from the collection.
     209     * <p>
     210     * This is the same as {@link #removeAllElements} followed by {@link #addAllElements} but
     211     * minimizes event firing and tries to keep the current selection.  Use this when all elements
     212     * are reinitialized programmatically like in an {@code autoCompBefore} event.
     213     *
     214     * @param newElements The new elements.
     215     */
     216    public void replaceAllElements(Collection<E> newElements) {
     217        Object oldSelected = selected;
     218        int index0 = elements.size();
     219        elements.clear();
     220        newElements.forEach(e -> doAddElement(e));
     221        int index1 = elements.size();
     222        int index2 = Math.min(index0, index1);
     223        if (0 < index2) {
     224            fireContentsChanged(this, 0, index2 - 1);
     225        }
     226        if (index2 < index0) {
     227            fireIntervalRemoved(this, index2, index0 - 1);
     228        }
     229        if (index2 < index1) {
     230            fireIntervalAdded(this, index2, index1 - 1);
     231        }
     232        // re-select the old selection if possible
     233        int index = elements.indexOf(oldSelected);
     234        setSelectedItem(index == -1 ? null : getElementAt(index));
     235    }
     236
     237    /**
    208238     * Adds an element to the top of the list.
    209239     * <p>
    210240     * If the element is already in the model, moves it to the top.  If the model gets too big,
  • src/org/openstreetmap/josm/gui/widgets/JosmTextField.java

     
    4242 */
    4343public class JosmTextField extends JTextField implements Destroyable, ComponentListener, FocusListener, PropertyChangeListener {
    4444
    45     private final PopupMenuLauncher launcher;
     45    private PopupMenuLauncher launcher;
    4646    private String hint;
    4747    private Icon icon;
    4848    private Point iconPos;
     
    241241    }
    242242
    243243    /**
     244     * Enables / disables the undo / redo functionality.
     245     * @param undoRedo enable if true
     246     */
     247    public void enableUndoRedo(boolean undoRedo) {
     248        launcher = TextContextualPopupMenu.enableMenuFor(this, undoRedo);
     249    }
     250
     251    /**
    244252     * Empties the internal undo manager.
    245253     * @since 14977
    246254     */
  • 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

     
    2020import org.openstreetmap.josm.data.osm.Way;
    2121import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2222import org.openstreetmap.josm.gui.tagging.TagEditorPanel;
    23 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    2423import org.openstreetmap.josm.testutils.JOSMTestRules;
    2524import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker;
    2625
     
    123122        OsmDataLayer layer = new OsmDataLayer(ds, "test", null);
    124123        IRelationEditor re = newRelationEditor(relation, layer);
    125124
    126         AutoCompletingTextField tfRole = GenericRelationEditor.buildRoleTextField(re);
    127         assertNotNull(tfRole);
    128 
    129125        TagEditorPanel tagEditorPanel = new TagEditorPanel(relation, null);
    130126
    131127        JPanel top = GenericRelationEditor.buildTagEditorPanel(tagEditorPanel);
  • 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}