Ticket #12224: 12224-v2.patch

File 12224-v2.patch, 36.0 KB (added by simon04, 10 years ago)
  • src/org/openstreetmap/josm/gui/MainApplication.java

    commit 356bbabfc3868e56819051d609caf7195c01388c
    Author: Simon Legner <Simon.Legner@gmail.com>
    Date:   Tue Jan 5 15:22:01 2016 +0100
    
        see #12224 - Dialog for "Search menu items"
    
    diff --git a/src/org/openstreetmap/josm/gui/MainApplication.java b/src/org/openstreetmap/josm/gui/MainApplication.java
    index f051cb3..4e6089e 100644
    a b public class MainApplication extends Main {  
    472472                splash.setVisible(false);
    473473                splash.dispose();
    474474                mainFrame.setVisible(true);
    475                 main.gettingStarted.requestFocusInWindow();
    476475            }
    477476        });
    478477
  • src/org/openstreetmap/josm/gui/MainMenu.java

    diff --git a/src/org/openstreetmap/josm/gui/MainMenu.java b/src/org/openstreetmap/josm/gui/MainMenu.java
    index 02939c6..3b417d3 100644
    a b import static org.openstreetmap.josm.tools.I18n.tr;  
    66import static org.openstreetmap.josm.tools.I18n.trc;
    77
    88import java.awt.Component;
    9 import java.awt.DefaultFocusTraversalPolicy;
    10 import java.awt.Dimension;
    119import java.awt.GraphicsEnvironment;
    12 import java.awt.event.ActionEvent;
    1310import java.awt.event.KeyEvent;
    14 import java.awt.event.KeyListener;
    1511import java.util.ArrayList;
    1612import java.util.HashMap;
    1713import java.util.List;
    1814import java.util.Locale;
    1915import java.util.Map;
    2016
    21 import javax.swing.Action;
    22 import javax.swing.Box;
    2317import javax.swing.JCheckBoxMenuItem;
    2418import javax.swing.JMenu;
    2519import javax.swing.JMenuBar;
    2620import javax.swing.JMenuItem;
    2721import javax.swing.JPopupMenu;
    2822import javax.swing.JSeparator;
    29 import javax.swing.JTextField;
    3023import javax.swing.KeyStroke;
    31 import javax.swing.MenuElement;
    32 import javax.swing.MenuSelectionManager;
    33 import javax.swing.event.DocumentEvent;
    34 import javax.swing.event.DocumentListener;
    3524import javax.swing.event.MenuEvent;
    3625import javax.swing.event.MenuListener;
    3726
    import org.openstreetmap.josm.actions.audio.AudioSlowerAction;  
    124113import org.openstreetmap.josm.actions.search.SearchAction;
    125114import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
    126115import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
     116import org.openstreetmap.josm.gui.dialogs.MenuItemSearchDialog;
    127117import org.openstreetmap.josm.gui.io.RecentlyOpenedFilesMenu;
    128118import org.openstreetmap.josm.gui.layer.Layer;
    129119import org.openstreetmap.josm.gui.mappaint.MapPaintMenu;
    import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference;  
    131121import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
    132122import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSearchAction;
    133123import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSearchPrimitiveDialog;
    134 import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField;
    135124import org.openstreetmap.josm.tools.Shortcut;
    136125
    137126/**
    public class MainMenu extends JMenuBar {  
    403392    public final DialogsToggleAction dialogsToggleAction = new DialogsToggleAction();
    404393    public FullscreenToggleAction fullscreenToggleAction;
    405394
    406     /**
    407      * Popup menu to display menu items search result.
    408      */
    409     private final JPopupMenu searchResultsMenu = new JPopupMenu();
    410 
    411395    /** this menu listener hides unnecessary JSeparators in a menu list but does not remove them.
    412396     * If at a later time the separators are required, they will be made visible again. Intended
    413397     * usage is make menus not look broken if separators are used to group the menu and some of
    public class MainMenu extends JMenuBar {  
    440424    };
    441425
    442426    /**
    443      * @return the default position of tnew top-level menus
     427     * @return the default position of new top-level menus
    444428     * @since 6088
    445429     */
    446430    public int getDefaultMenuPos() {
    public class MainMenu extends JMenuBar {  
    817801            }
    818802        });
    819803
    820         helpMenu.add(statusreport);
    821         helpMenu.add(reportbug);
     804        add(helpMenu, new MenuItemSearchDialog.Action());
     805        helpMenu.addSeparator();
     806        add(helpMenu, statusreport);
     807        add(helpMenu, reportbug);
    822808        helpMenu.addSeparator();
    823809
    824810        // FIXME why is help not a JosmAction?
    825811        helpMenu.add(help).setAccelerator(Shortcut.registerShortcut("system:help", tr("Help"), KeyEvent.VK_F1,
    826812                Shortcut.DIRECT).getKeyStroke());
    827813        add(helpMenu, about);
    828         add(Box.createHorizontalGlue());
    829         final DisableShortcutsOnFocusGainedTextField searchField = createSearchField();
    830         add(searchField);
    831 
    832         // Do not let search field take the focus automatically
    833         setFocusTraversalPolicyProvider(true);
    834         setFocusTraversalPolicy(new DefaultFocusTraversalPolicy() {
    835             @Override
    836             protected boolean accept(Component aComponent) {
    837                 return super.accept(aComponent) && !searchField.equals(aComponent);
    838             }
    839         });
    840814
    841815        windowMenu.addMenuListener(menuSeparatorHandler);
    842816
    public class MainMenu extends JMenuBar {  
    855829    }
    856830
    857831    /**
    858      * Create search field.
    859      * @return the search field
    860      */
    861     private DisableShortcutsOnFocusGainedTextField createSearchField() {
    862         DisableShortcutsOnFocusGainedTextField searchField = new DisableShortcutsOnFocusGainedTextField() {
    863             @Override
    864             public Dimension getPreferredSize() {
    865                 // JMenuBar uses a BoxLayout and it doesn't seem possible to specify a size factor,
    866                 // so compute the preferred size dynamically
    867                 return new Dimension(Math.min(200, Math.max(25, getMaximumAvailableWidth())),
    868                         helpMenu.getPreferredSize().height);
    869             }
    870         };
    871         Shortcut searchFieldShortcut = Shortcut.registerShortcut("menu:search-field", tr("Search menu items"), KeyEvent.VK_R, Shortcut.MNEMONIC);
    872         searchFieldShortcut.setFocusAccelerator(searchField);
    873         searchField.setEditable(true);
    874         searchField.setMaximumSize(new Dimension(200, helpMenu.getPreferredSize().height));
    875         searchField.setHint(tr("Search menu items"));
    876         searchField.setToolTipText(Main.platform.makeTooltip(tr("Search menu items"), searchFieldShortcut));
    877         searchField.addKeyListener(new SearchFieldKeyListener());
    878         searchField.getDocument().addDocumentListener(new SearchFieldTextListener(this, searchField));
    879         return searchField;
    880     }
    881 
    882     /**
    883832     * Search main menu for items with {@code textToFind} in title.
    884833     * @param textToFind The text to find
     834     * @param skipPresets whether to skip the {@link #presetsMenu} in the search
    885835     * @return not null list of found menu items.
    886836     */
    887     private List<JMenuItem> findMenuItems(String textToFind) {
    888         // Explicitely use default locale in this case, because we're looking for translated strings
     837    public List<JMenuItem> findMenuItems(String textToFind, boolean skipPresets) {
     838        // Explicitly use default locale in this case, because we're looking for translated strings
    889839        textToFind = textToFind.toLowerCase(Locale.getDefault());
    890840        List<JMenuItem> result = new ArrayList<>();
    891 
    892         // Iterate over main menus
    893         for (MenuElement menuElement : getSubElements()) {
    894             if (!(menuElement instanceof JMenu)) continue;
    895 
    896             JMenu mainMenuItem = (JMenu) menuElement;
    897             if (mainMenuItem.getAction() != null && mainMenuItem.getText().toLowerCase(Locale.getDefault()).contains(textToFind)) {
    898                 result.add(mainMenuItem);
     841        for (int i = 0; i < getMenuCount(); i++) {
     842            if (getMenu(i) != null && (!skipPresets || presetsMenu != getMenu(i))) {
     843                findMenuItems(getMenu(i), textToFind, result);
    899844            }
    900 
    901             //Search recursively
    902             findMenuItems(mainMenuItem, textToFind, result);
    903845        }
    904846        return result;
    905847    }
    public class MainMenu extends JMenuBar {  
    909851     * contains {@code textToFind} it's appended to result.
    910852     * @param menu menu in which search will be performed
    911853     * @param textToFind The text to find
    912      * @param result resulting list ofmenu items
     854     * @param result resulting list of menu items
    913855     */
    914856    private static void findMenuItems(final JMenu menu, final String textToFind, final List<JMenuItem> result) {
    915857        for (int i = 0; i < menu.getItemCount(); i++) {
    916858            JMenuItem menuItem = menu.getItem(i);
    917859            if (menuItem == null) continue;
    918860
    919             // Explicitely use default locale in this case, because we're looking for translated strings
     861            // Explicitly use default locale in this case, because we're looking for translated strings
    920862            if (menuItem.getAction() != null && menuItem.getText().toLowerCase(Locale.getDefault()).contains(textToFind)) {
    921863                result.add(menuItem);
    922864            }
    public class MainMenu extends JMenuBar {  
    978920        }
    979921    }
    980922
    981     /**
    982      * This listener is designed to handle ENTER key pressed in menu search field.
    983      * When user presses Enter key then selected item of "searchResultsMenu" is triggered.
    984      */
    985     private static class SearchFieldKeyListener implements KeyListener {
    986 
    987         @Override
    988         public void keyPressed(KeyEvent e) {
    989             if (e.getKeyCode() == KeyEvent.VK_ENTER) {
    990                 // On ENTER selected menu item must be triggered
    991                 MenuElement[] selection = MenuSelectionManager.defaultManager().getSelectedPath();
    992                 if (selection.length > 1) {
    993                     MenuElement selectedElement = selection[selection.length-1];
    994                     if (selectedElement instanceof JMenuItem) {
    995                         JMenuItem selectedItem = (JMenuItem) selectedElement;
    996                         Action menuAction = selectedItem.getAction();
    997                         menuAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null));
    998                         if (Main.isDebugEnabled()) {
    999                             Main.debug(getClass().getName()+" consuming event "+e);
    1000                         }
    1001                         e.consume();
    1002                     }
    1003                 }
    1004             }
    1005         }
    1006 
    1007         @Override
    1008         public void keyTyped(KeyEvent e) {
    1009             // Not used
    1010         }
    1011 
    1012         @Override
    1013         public void keyReleased(KeyEvent e) {
    1014             // Not used
    1015         }
    1016     }
    1017 
    1018     private class SearchFieldTextListener implements DocumentListener {
    1019         private final JTextField searchField;
    1020         private final MainMenu mainMenu;
    1021         private String currentSearchText;
    1022 
    1023         SearchFieldTextListener(MainMenu mainMenu, JTextField searchField) {
    1024             this.mainMenu = mainMenu;
    1025             this.searchField = searchField;
    1026         }
    1027 
    1028         @Override
    1029         public void insertUpdate(DocumentEvent e) {
    1030             doSearch(searchField.getText());
    1031         }
    1032 
    1033         @Override
    1034         public void removeUpdate(DocumentEvent e) {
    1035             doSearch(searchField.getText());
    1036         }
    1037 
    1038         @Override
    1039         public void changedUpdate(DocumentEvent e) {
    1040             doSearch(searchField.getText());
    1041         }
    1042 
    1043         //TODO: perform some delay (maybe 200 ms) before actual searching.
    1044         void doSearch(String searchTerm) {
    1045             // Explicitely use default locale in this case, because we're looking for translated strings
    1046             searchTerm = searchTerm.trim().toLowerCase(Locale.getDefault());
    1047 
    1048             if (searchTerm.equals(currentSearchText)) {
    1049                 return;
    1050             }
    1051             currentSearchText = searchTerm;
    1052             if (searchTerm.isEmpty()) {
    1053                 // No text to search
    1054                 hideMenu();
    1055                 return;
    1056             }
    1057 
    1058             List<JMenuItem> searchResult = mainMenu.findMenuItems(currentSearchText);
    1059             if (searchResult.isEmpty()) {
    1060                 // Nothing found
    1061                 hideMenu();
    1062                 return;
    1063             }
    1064 
    1065             if (searchResult.size() > 20) {
    1066                 // Too many items found...
    1067                 searchResult = searchResult.subList(0, 20);
    1068             }
    1069 
    1070             // Update Popup menu
    1071             searchResultsMenu.removeAll();
    1072             for (JMenuItem foundItem : searchResult) {
    1073                 searchResultsMenu.add(foundItem.getText()).setAction(foundItem.getAction());
    1074             }
    1075             // Put menu right under search field
    1076             searchResultsMenu.pack();
    1077             searchResultsMenu.show(mainMenu, searchField.getX(), searchField.getY() + searchField.getHeight());
    1078 
    1079             // This is tricky. User still is able to edit search text. While Up and Down keys are handled by Popup Menu.
    1080             searchField.requestFocusInWindow();
    1081         }
    1082 
    1083         private void hideMenu() {
    1084             searchResultsMenu.setVisible(false);
    1085         }
    1086     }
    1087923}
  • new file src/org/openstreetmap/josm/gui/dialogs/MenuItemSearchDialog.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/MenuItemSearchDialog.java b/src/org/openstreetmap/josm/gui/dialogs/MenuItemSearchDialog.java
    new file mode 100644
    index 0000000..2ab0a94
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.dialogs;
     3
     4import org.openstreetmap.josm.Main;
     5import org.openstreetmap.josm.actions.JosmAction;
     6import org.openstreetmap.josm.gui.ExtendedDialog;
     7import org.openstreetmap.josm.gui.MainMenu;
     8import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel;
     9import org.openstreetmap.josm.tools.Shortcut;
     10
     11import javax.swing.*;
     12import java.awt.*;
     13import java.awt.event.ActionEvent;
     14import java.awt.event.ActionListener;
     15import java.awt.event.KeyEvent;
     16
     17import static org.openstreetmap.josm.tools.I18n.tr;
     18
     19public class MenuItemSearchDialog extends ExtendedDialog {
     20
     21    private final Selector selector;
     22    private static final MenuItemSearchDialog INSTANCE = new MenuItemSearchDialog(Main.main.menu);
     23
     24    private MenuItemSearchDialog(MainMenu menu) {
     25        super(Main.parent, tr("Search menu items"), new String[]{tr("Select"), tr("Cancel")});
     26        this.selector = new Selector(menu);
     27        this.selector.setDblClickListener(new ActionListener() {
     28            @Override
     29            public void actionPerformed(ActionEvent e) {
     30                buttonAction(0, null);
     31            }
     32        });
     33        setContent(selector);
     34        setPreferredSize(new Dimension(600, 300));
     35    }
     36
     37    /**
     38     * Returns the unique instance of {@code MenuItemSearchDialog}.
     39     *
     40     * @return the unique instance of {@code MenuItemSearchDialog}.
     41     */
     42    public static synchronized MenuItemSearchDialog getInstance() {
     43        return INSTANCE;
     44    }
     45
     46    @Override
     47    public ExtendedDialog showDialog() {
     48        selector.init();
     49        super.showDialog();
     50        selector.clearSelection();
     51        return this;
     52    }
     53
     54    @Override
     55    protected void buttonAction(int buttonIndex, ActionEvent evt) {
     56        super.buttonAction(buttonIndex, evt);
     57        if (buttonIndex == 0 && selector.getSelectedItem() != null && selector.getSelectedItem().isEnabled()) {
     58            selector.getSelectedItem().getAction().actionPerformed(evt);
     59        }
     60    }
     61
     62    private static class Selector extends SearchTextResultListPanel<JMenuItem> {
     63
     64        private final MainMenu menu;
     65
     66        public Selector(MainMenu menu) {
     67            super();
     68            this.menu = menu;
     69            lsResult.setCellRenderer(new CellRenderer());
     70            lsResult.setSelectionModel(new DefaultListSelectionModel() {
     71
     72            });
     73        }
     74
     75        public JMenuItem getSelectedItem() {
     76            final JMenuItem selected = lsResult.getSelectedValue();
     77            if (selected != null) {
     78                return selected;
     79            } else if (!lsResultModel.isEmpty()) {
     80                return lsResultModel.getElementAt(0);
     81            } else {
     82                return null;
     83            }
     84        }
     85
     86        @Override
     87        protected void filterItems() {
     88            lsResultModel.setItems(menu.findMenuItems(edSearchText.getText(), true));
     89        }
     90    }
     91
     92    private static class CellRenderer implements ListCellRenderer<JMenuItem> {
     93
     94        private final DefaultListCellRenderer def = new DefaultListCellRenderer();
     95
     96        @Override
     97        public Component getListCellRendererComponent(JList<? extends JMenuItem> list, JMenuItem value, int index, boolean isSelected, boolean cellHasFocus) {
     98            final JLabel label = (JLabel) def.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
     99            label.setText(value.getText());
     100            label.setIcon(value.getIcon());
     101            label.setEnabled(value.isEnabled());
     102            final JMenuItem item = new JMenuItem(value.getText());
     103            item.setAction(value.getAction());
     104            if (isSelected) {
     105                item.setBackground(list.getSelectionBackground());
     106                item.setForeground(list.getSelectionForeground());
     107            } else {
     108                item.setBackground(list.getBackground());
     109                item.setForeground(list.getForeground());
     110            }
     111            return item;
     112        }
     113    }
     114
     115    public static class Action extends JosmAction {
     116
     117        public Action() {
     118            super(tr("Search menu items"), "dialogs/search", null,
     119                    Shortcut.registerShortcut("help:search-items", "Search menu items", KeyEvent.VK_SPACE, Shortcut.CTRL), false);
     120        }
     121
     122        @Override
     123        public void actionPerformed(ActionEvent e) {
     124            MenuItemSearchDialog.getInstance().showDialog();
     125        }
     126    }
     127}
  • src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java

    diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java
    index 88d8ff8..840c657 100644
    a b import java.awt.BorderLayout;  
    77import java.awt.Component;
    88import java.awt.Dimension;
    99import java.awt.event.ActionEvent;
    10 import java.awt.event.ActionListener;
    1110import java.awt.event.ItemEvent;
    1211import java.awt.event.ItemListener;
    13 import java.awt.event.KeyAdapter;
    14 import java.awt.event.KeyEvent;
    15 import java.awt.event.MouseAdapter;
    16 import java.awt.event.MouseEvent;
    1712import java.util.ArrayList;
    1813import java.util.Collection;
    1914import java.util.Collections;
    import java.util.Objects;  
    2621import java.util.Set;
    2722
    2823import javax.swing.AbstractAction;
    29 import javax.swing.AbstractListModel;
    3024import javax.swing.Action;
    3125import javax.swing.BoxLayout;
    3226import javax.swing.DefaultListCellRenderer;
    import javax.swing.JLabel;  
    3630import javax.swing.JList;
    3731import javax.swing.JPanel;
    3832import javax.swing.JPopupMenu;
    39 import javax.swing.JScrollPane;
    4033import javax.swing.ListCellRenderer;
    41 import javax.swing.event.DocumentEvent;
    42 import javax.swing.event.DocumentListener;
    4334import javax.swing.event.ListSelectionEvent;
    4435import javax.swing.event.ListSelectionListener;
    4536
    import org.openstreetmap.josm.gui.tagging.presets.items.Key;  
    5344import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
    5445import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
    5546import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
    56 import org.openstreetmap.josm.gui.widgets.JosmTextField;
    5747import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
     48import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel;
    5849import org.openstreetmap.josm.tools.Predicate;
    5950import org.openstreetmap.josm.tools.Utils;
    6051
    import org.openstreetmap.josm.tools.Utils;  
    6253 * GUI component to select tagging preset: the list with filter and two checkboxes
    6354 * @since 6068
    6455 */
    65 public class TaggingPresetSelector extends JPanel implements SelectionChangedListener {
     56public class TaggingPresetSelector extends SearchTextResultListPanel<TaggingPreset> implements SelectionChangedListener {
    6657
    6758    private static final int CLASSIFICATION_IN_FAVORITES = 300;
    6859    private static final int CLASSIFICATION_NAME_MATCH = 300;
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    7263    private static final BooleanProperty SEARCH_IN_TAGS = new BooleanProperty("taggingpreset.dialog.search-in-tags", true);
    7364    private static final BooleanProperty ONLY_APPLICABLE  = new BooleanProperty("taggingpreset.dialog.only-applicable-to-selection", true);
    7465
    75     private final JosmTextField edSearchText;
    76     private final JList<TaggingPreset> lsResult;
    7766    private final JCheckBox ckOnlyApplicable;
    7867    private final JCheckBox ckSearchInTags;
    7968    private final Set<TaggingPresetType> typesInSelection = EnumSet.noneOf(TaggingPresetType.class);
    8069    private boolean typesInSelectionDirty = true;
    8170    private final transient PresetClassifications classifications = new PresetClassifications();
    82     private final ResultListModel lsResultModel = new ResultListModel();
    83 
    84     private final transient List<ListSelectionListener> listSelectionListeners = new ArrayList<>();
    85 
    86     private transient ActionListener dblClickListener;
    87     private transient ActionListener clickListener;
    8871
    8972    private static class ResultListCellRenderer implements ListCellRenderer<TaggingPreset> {
    9073        private final DefaultListCellRenderer def = new DefaultListCellRenderer();
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    9881        }
    9982    }
    10083
    101     private static class ResultListModel extends AbstractListModel<TaggingPreset> {
    102 
    103         private transient List<PresetClassification> presets = new ArrayList<>();
    104 
    105         public synchronized void setPresets(List<PresetClassification> presets) {
    106             this.presets = presets;
    107             fireContentsChanged(this, 0, Integer.MAX_VALUE);
    108         }
    109 
    110         @Override
    111         public synchronized TaggingPreset getElementAt(int index) {
    112             return presets.get(index).preset;
    113         }
    114 
    115         @Override
    116         public synchronized int getSize() {
    117             return presets.size();
    118         }
    119 
    120         public synchronized boolean isEmpty() {
    121             return presets.isEmpty();
    122         }
    123     }
    124 
    12584    /**
    12685     * Computes the match ration of a {@link TaggingPreset} wrt. a searchString.
    12786     */
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    218177     * @param displaySearchInTags if {@code true} display "Search in tags" checkbox
    219178     */
    220179    public TaggingPresetSelector(boolean displayOnlyApplicable, boolean displaySearchInTags) {
    221         super(new BorderLayout());
    222         classifications.loadPresets(TaggingPresets.getTaggingPresets());
    223 
    224         edSearchText = new JosmTextField();
    225         edSearchText.getDocument().addDocumentListener(new DocumentListener() {
    226             @Override
    227             public void removeUpdate(DocumentEvent e) {
    228                 filterPresets();
    229             }
    230 
    231             @Override
    232             public void insertUpdate(DocumentEvent e) {
    233                 filterPresets();
    234             }
    235 
    236             @Override
    237             public void changedUpdate(DocumentEvent e) {
    238                 filterPresets();
    239             }
    240         });
    241         edSearchText.addKeyListener(new KeyAdapter() {
    242             @Override
    243             public void keyPressed(KeyEvent e) {
    244                 switch (e.getKeyCode()) {
    245                 case KeyEvent.VK_DOWN:
    246                     selectPreset(lsResult.getSelectedIndex() + 1);
    247                     break;
    248                 case KeyEvent.VK_UP:
    249                     selectPreset(lsResult.getSelectedIndex() - 1);
    250                     break;
    251                 case KeyEvent.VK_PAGE_DOWN:
    252                     selectPreset(lsResult.getSelectedIndex() + 10);
    253                     break;
    254                 case KeyEvent.VK_PAGE_UP:
    255                     selectPreset(lsResult.getSelectedIndex() - 10);
    256                     break;
    257                 case KeyEvent.VK_HOME:
    258                     selectPreset(0);
    259                     break;
    260                 case KeyEvent.VK_END:
    261                     selectPreset(lsResultModel.getSize());
    262                     break;
    263                 }
    264             }
    265         });
    266         add(edSearchText, BorderLayout.NORTH);
    267 
    268         lsResult = new JList<>(lsResultModel);
     180        super();
    269181        lsResult.setCellRenderer(new ResultListCellRenderer());
    270         lsResult.addMouseListener(new MouseAdapter() {
    271             @Override
    272             public void mouseClicked(MouseEvent e) {
    273                 if (e.getClickCount() > 1) {
    274                     if (dblClickListener != null)
    275                         dblClickListener.actionPerformed(null);
    276                 } else {
    277                     if (clickListener != null)
    278                         clickListener.actionPerformed(null);
    279                 }
    280             }
    281         });
    282         add(new JScrollPane(lsResult), BorderLayout.CENTER);
     182        classifications.loadPresets(TaggingPresets.getTaggingPresets());
    283183
    284184        JPanel pnChecks = new JPanel();
    285185        pnChecks.setLayout(new BoxLayout(pnChecks, BoxLayout.Y_AXIS));
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    291191            ckOnlyApplicable.addItemListener(new ItemListener() {
    292192                @Override
    293193                public void itemStateChanged(ItemEvent e) {
    294                     filterPresets();
     194                    filterItems();
    295195                }
    296196            });
    297197        } else {
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    305205            ckSearchInTags.addItemListener(new ItemListener() {
    306206                @Override
    307207                public void itemStateChanged(ItemEvent e) {
    308                     filterPresets();
     208                    filterItems();
    309209                }
    310210            });
    311211            pnChecks.add(ckSearchInTags);
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    316216        add(pnChecks, BorderLayout.SOUTH);
    317217
    318218        setPreferredSize(new Dimension(400, 300));
    319         filterPresets();
     219        filterItems();
    320220        JPopupMenu popupMenu = new JPopupMenu();
    321221        popupMenu.add(new AbstractAction(tr("Add toolbar button")) {
    322222            @Override
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    330230        lsResult.addMouseListener(new PopupMenuLauncher(popupMenu));
    331231    }
    332232
    333     private synchronized void selectPreset(int newIndex) {
    334         if (newIndex < 0) {
    335             newIndex = 0;
    336         }
    337         if (newIndex > lsResultModel.getSize() - 1) {
    338             newIndex = lsResultModel.getSize() - 1;
    339         }
    340         lsResult.setSelectedIndex(newIndex);
    341         lsResult.ensureIndexIsVisible(newIndex);
    342     }
    343 
    344233    /**
    345234     * Search expression can be in form: "group1/group2/name" where names can contain multiple words
    346235     */
    347     private synchronized void filterPresets() {
     236    @Override
     237    protected synchronized void filterItems() {
    348238        //TODO Save favorites to file
    349239        String text = edSearchText.getText().toLowerCase(Locale.ENGLISH);
    350240        boolean onlyApplicable = ckOnlyApplicable != null && ckOnlyApplicable.isSelected();
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    356246                text, onlyApplicable, inTags, getTypesInSelection(), selected);
    357247
    358248        final TaggingPreset oldPreset = lsResult.getSelectedValue();
    359         lsResultModel.setPresets(result);
     249        lsResultModel.setItems(Utils.transform(result, new Utils.Function<PresetClassification, TaggingPreset>() {
     250            @Override
     251            public TaggingPreset apply(PresetClassification x) {
     252                return x.preset;
     253            }
     254        }));
    360255        final TaggingPreset newPreset = lsResult.getSelectedValue();
    361256        if (!Objects.equals(oldPreset, newPreset)) {
    362257            int[] indices = lsResult.getSelectedIndices();
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    490385        typesInSelectionDirty = true;
    491386    }
    492387
     388    @Override
    493389    public synchronized void init() {
    494390        if (ckOnlyApplicable != null) {
    495391            ckOnlyApplicable.setEnabled(!getTypesInSelection().isEmpty());
    496392            ckOnlyApplicable.setSelected(!getTypesInSelection().isEmpty() && ONLY_APPLICABLE.get());
    497393        }
    498         listSelectionListeners.clear();
    499         edSearchText.setText("");
    500         filterPresets();
     394        super.init();
    501395    }
    502396
    503397    public void init(Collection<TaggingPreset> presets) {
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    506400        init();
    507401    }
    508402
    509     public synchronized void clearSelection() {
    510         lsResult.getSelectionModel().clearSelection();
    511     }
    512 
    513403    /**
    514404     * Save checkbox values in preferences for future reuse
    515405     */
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    546436    public synchronized void setSelectedPreset(TaggingPreset p) {
    547437        lsResult.setSelectedValue(p, true);
    548438    }
    549 
    550     public synchronized int getItemCount() {
    551         return lsResultModel.getSize();
    552     }
    553 
    554     public void setDblClickListener(ActionListener dblClickListener) {
    555         this.dblClickListener = dblClickListener;
    556     }
    557 
    558     public void setClickListener(ActionListener clickListener) {
    559         this.clickListener = clickListener;
    560     }
    561 
    562     /**
    563      * Adds a selection listener to the presets list.
    564      * @param selectListener The list selection listener
    565      * @since 7412
    566      */
    567     public synchronized void addSelectionListener(ListSelectionListener selectListener) {
    568         lsResult.getSelectionModel().addListSelectionListener(selectListener);
    569         listSelectionListeners.add(selectListener);
    570     }
    571 
    572     /**
    573      * Removes a selection listener from the presets list.
    574      * @param selectListener The list selection listener
    575      * @since 7412
    576      */
    577     public synchronized void removeSelectionListener(ListSelectionListener selectListener) {
    578         listSelectionListeners.remove(selectListener);
    579         lsResult.getSelectionModel().removeListSelectionListener(selectListener);
    580     }
    581439}
  • new file src/org/openstreetmap/josm/gui/widgets/SearchTextResultListPanel.java

    diff --git a/src/org/openstreetmap/josm/gui/widgets/SearchTextResultListPanel.java b/src/org/openstreetmap/josm/gui/widgets/SearchTextResultListPanel.java
    new file mode 100644
    index 0000000..94b5f11
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.widgets;
     3
     4import javax.swing.*;
     5import javax.swing.event.DocumentEvent;
     6import javax.swing.event.DocumentListener;
     7import javax.swing.event.ListSelectionListener;
     8import java.awt.*;
     9import java.awt.event.ActionListener;
     10import java.awt.event.KeyAdapter;
     11import java.awt.event.KeyEvent;
     12import java.awt.event.MouseAdapter;
     13import java.awt.event.MouseEvent;
     14import java.util.*;
     15import java.util.List;
     16
     17public abstract class SearchTextResultListPanel<T> extends JPanel {
     18
     19    protected final JosmTextField edSearchText;
     20    protected final JList<T> lsResult;
     21    protected final ResultListModel<T> lsResultModel = new ResultListModel<>();
     22
     23    protected final transient List<ListSelectionListener> listSelectionListeners = new ArrayList<>();
     24
     25    private transient ActionListener dblClickListener;
     26    private transient ActionListener clickListener;
     27
     28    protected abstract void filterItems();
     29
     30    public SearchTextResultListPanel() {
     31        super(new BorderLayout());
     32
     33        edSearchText = new JosmTextField();
     34        edSearchText.getDocument().addDocumentListener(new DocumentListener() {
     35            @Override
     36            public void removeUpdate(DocumentEvent e) {
     37                filterItems();
     38            }
     39
     40            @Override
     41            public void insertUpdate(DocumentEvent e) {
     42                filterItems();
     43            }
     44
     45            @Override
     46            public void changedUpdate(DocumentEvent e) {
     47                filterItems();
     48            }
     49        });
     50        edSearchText.addKeyListener(new KeyAdapter() {
     51            @Override
     52            public void keyPressed(KeyEvent e) {
     53                switch (e.getKeyCode()) {
     54                    case KeyEvent.VK_DOWN:
     55                        selectItem(lsResult.getSelectedIndex() + 1);
     56                        break;
     57                    case KeyEvent.VK_UP:
     58                        selectItem(lsResult.getSelectedIndex() - 1);
     59                        break;
     60                    case KeyEvent.VK_PAGE_DOWN:
     61                        selectItem(lsResult.getSelectedIndex() + 10);
     62                        break;
     63                    case KeyEvent.VK_PAGE_UP:
     64                        selectItem(lsResult.getSelectedIndex() - 10);
     65                        break;
     66                    case KeyEvent.VK_HOME:
     67                        selectItem(0);
     68                        break;
     69                    case KeyEvent.VK_END:
     70                        selectItem(lsResultModel.getSize());
     71                        break;
     72                }
     73            }
     74        });
     75        add(edSearchText, BorderLayout.NORTH);
     76
     77        lsResult = new JList<>(lsResultModel);
     78        lsResult.addMouseListener(new MouseAdapter() {
     79            @Override
     80            public void mouseClicked(MouseEvent e) {
     81                if (e.getClickCount() > 1) {
     82                    if (dblClickListener != null)
     83                        dblClickListener.actionPerformed(null);
     84                } else {
     85                    if (clickListener != null)
     86                        clickListener.actionPerformed(null);
     87                }
     88            }
     89        });
     90        add(new JScrollPane(lsResult), BorderLayout.CENTER);
     91    }
     92
     93    protected static class ResultListModel<T> extends AbstractListModel<T> {
     94
     95        private transient List<T> items = new ArrayList<>();
     96
     97        public synchronized void setItems(List<T> items) {
     98            this.items = items;
     99            fireContentsChanged(this, 0, Integer.MAX_VALUE);
     100        }
     101
     102        @Override
     103        public synchronized T getElementAt(int index) {
     104            return items.get(index);
     105        }
     106
     107        @Override
     108        public synchronized int getSize() {
     109            return items.size();
     110        }
     111
     112        public synchronized boolean isEmpty() {
     113            return items.isEmpty();
     114        }
     115    }
     116
     117    public synchronized void init() {
     118        listSelectionListeners.clear();
     119        edSearchText.setText("");
     120        filterItems();
     121    }
     122
     123    private synchronized void selectItem(int newIndex) {
     124        if (newIndex < 0) {
     125            newIndex = 0;
     126        }
     127        if (newIndex > lsResultModel.getSize() - 1) {
     128            newIndex = lsResultModel.getSize() - 1;
     129        }
     130        lsResult.setSelectedIndex(newIndex);
     131        lsResult.ensureIndexIsVisible(newIndex);
     132    }
     133
     134    public synchronized void clearSelection() {
     135        lsResult.clearSelection();
     136    }
     137
     138    public synchronized int getItemCount() {
     139        return lsResultModel.getSize();
     140    }
     141
     142    public void setDblClickListener(ActionListener dblClickListener) {
     143        this.dblClickListener = dblClickListener;
     144    }
     145
     146    public void setClickListener(ActionListener clickListener) {
     147        this.clickListener = clickListener;
     148    }
     149
     150    /**
     151     * Adds a selection listener to the presets list.
     152     *
     153     * @param selectListener The list selection listener
     154     * @since 7412
     155     */
     156    public synchronized void addSelectionListener(ListSelectionListener selectListener) {
     157        lsResult.getSelectionModel().addListSelectionListener(selectListener);
     158        listSelectionListeners.add(selectListener);
     159    }
     160
     161    /**
     162     * Removes a selection listener from the presets list.
     163     *
     164     * @param selectListener The list selection listener
     165     * @since 7412
     166     */
     167    public synchronized void removeSelectionListener(ListSelectionListener selectListener) {
     168        listSelectionListeners.remove(selectListener);
     169        lsResult.getSelectionModel().removeListSelectionListener(selectListener);
     170    }
     171}