Ticket #12224: 12224.patch

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

    commit c12fc123f2111694e58c8a8a36320dbe38b0b15b
    Author: Simon Legner <Simon.Legner@gmail.com>
    Date:   Wed Dec 23 16:25:33 2015 +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 5198e66..64c6842 100644
    a b public class MainApplication extends Main {  
    471471                splash.setVisible(false);
    472472                splash.dispose();
    473473                mainFrame.setVisible(true);
    474                 main.gettingStarted.requestFocusInWindow();
    475474            }
    476475        });
    477476
  • 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 f996248..82ab5b7 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 {  
    809793            }
    810794        });
    811795
     796        helpMenu.add(new MenuItemSearchDialog.Action());
     797        helpMenu.addSeparator();
    812798        helpMenu.add(statusreport);
    813799        helpMenu.add(reportbug);
    814800        helpMenu.addSeparator();
    public class MainMenu extends JMenuBar {  
    817803        helpMenu.add(help).setAccelerator(Shortcut.registerShortcut("system:help", tr("Help"), KeyEvent.VK_F1,
    818804                Shortcut.DIRECT).getKeyStroke());
    819805        add(helpMenu, about);
    820         add(Box.createHorizontalGlue());
    821         final DisableShortcutsOnFocusGainedTextField searchField = createSearchField();
    822         add(searchField);
    823 
    824         // Do not let search field take the focus automatically
    825         setFocusTraversalPolicyProvider(true);
    826         setFocusTraversalPolicy(new DefaultFocusTraversalPolicy() {
    827             @Override
    828             protected boolean accept(Component aComponent) {
    829                 return super.accept(aComponent) && !searchField.equals(aComponent);
    830             }
    831         });
    832806
    833807        windowMenu.addMenuListener(menuSeparatorHandler);
    834808
    public class MainMenu extends JMenuBar {  
    847821    }
    848822
    849823    /**
    850      * Create search field.
    851      * @return the search field
    852      */
    853     private DisableShortcutsOnFocusGainedTextField createSearchField() {
    854         DisableShortcutsOnFocusGainedTextField searchField = new DisableShortcutsOnFocusGainedTextField() {
    855             @Override
    856             public Dimension getPreferredSize() {
    857                 // JMenuBar uses a BoxLayout and it doesn't seem possible to specify a size factor,
    858                 // so compute the preferred size dynamically
    859                 return new Dimension(Math.min(200, Math.max(25, getMaximumAvailableWidth())),
    860                         helpMenu.getPreferredSize().height);
    861             }
    862         };
    863         Shortcut searchFieldShortcut = Shortcut.registerShortcut("menu:search-field", tr("Search menu items"), KeyEvent.VK_R, Shortcut.MNEMONIC);
    864         searchFieldShortcut.setFocusAccelerator(searchField);
    865         searchField.setEditable(true);
    866         searchField.setMaximumSize(new Dimension(200, helpMenu.getPreferredSize().height));
    867         searchField.setHint(tr("Search menu items"));
    868         searchField.setToolTipText(Main.platform.makeTooltip(tr("Search menu items"), searchFieldShortcut));
    869         searchField.addKeyListener(new SearchFieldKeyListener());
    870         searchField.getDocument().addDocumentListener(new SearchFieldTextListener(this, searchField));
    871         return searchField;
    872     }
    873 
    874     /**
    875824     * Search main menu for items with {@code textToFind} in title.
    876825     * @param textToFind The text to find
     826     * @param skipPresets whether to skip the {@link #presetsMenu} in the search
    877827     * @return not null list of found menu items.
    878828     */
    879     private List<JMenuItem> findMenuItems(String textToFind) {
    880         // Explicitely use default locale in this case, because we're looking for translated strings
     829    public List<JMenuItem> findMenuItems(String textToFind, boolean skipPresets) {
     830        // Explicitly use default locale in this case, because we're looking for translated strings
    881831        textToFind = textToFind.toLowerCase(Locale.getDefault());
    882832        List<JMenuItem> result = new ArrayList<>();
    883 
    884         // Iterate over main menus
    885         for (MenuElement menuElement : getSubElements()) {
    886             if (!(menuElement instanceof JMenu)) continue;
    887 
    888             JMenu mainMenuItem = (JMenu) menuElement;
    889             if (mainMenuItem.getAction() != null && mainMenuItem.getText().toLowerCase(Locale.getDefault()).contains(textToFind)) {
    890                 result.add(mainMenuItem);
     833        for (int i = 0; i < getMenuCount(); i++) {
     834            if (getMenu(i) != null && (!skipPresets || presetsMenu != getMenu(i))) {
     835                findMenuItems(getMenu(i), textToFind, result);
    891836            }
    892 
    893             //Search recursively
    894             findMenuItems(mainMenuItem, textToFind, result);
    895837        }
    896838        return result;
    897839    }
    public class MainMenu extends JMenuBar {  
    901843     * contains {@code textToFind} it's appended to result.
    902844     * @param menu menu in which search will be performed
    903845     * @param textToFind The text to find
    904      * @param result resulting list ofmenu items
     846     * @param result resulting list of menu items
    905847     */
    906848    private static void findMenuItems(final JMenu menu, final String textToFind, final List<JMenuItem> result) {
    907849        for (int i = 0; i < menu.getItemCount(); i++) {
    908850            JMenuItem menuItem = menu.getItem(i);
    909851            if (menuItem == null) continue;
    910852
    911             // Explicitely use default locale in this case, because we're looking for translated strings
     853            // Explicitly use default locale in this case, because we're looking for translated strings
    912854            if (menuItem.getAction() != null && menuItem.getText().toLowerCase(Locale.getDefault()).contains(textToFind)) {
    913855                result.add(menuItem);
    914856            }
    public class MainMenu extends JMenuBar {  
    970912        }
    971913    }
    972914
    973     /**
    974      * This listener is designed to handle ENTER key pressed in menu search field.
    975      * When user presses Enter key then selected item of "searchResultsMenu" is triggered.
    976      */
    977     private static class SearchFieldKeyListener implements KeyListener {
    978 
    979         @Override
    980         public void keyPressed(KeyEvent e) {
    981             if (e.getKeyCode() == KeyEvent.VK_ENTER) {
    982                 // On ENTER selected menu item must be triggered
    983                 MenuElement[] selection = MenuSelectionManager.defaultManager().getSelectedPath();
    984                 if (selection.length > 1) {
    985                     MenuElement selectedElement = selection[selection.length-1];
    986                     if (selectedElement instanceof JMenuItem) {
    987                         JMenuItem selectedItem = (JMenuItem) selectedElement;
    988                         Action menuAction = selectedItem.getAction();
    989                         menuAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null));
    990                         if (Main.isDebugEnabled()) {
    991                             Main.debug(getClass().getName()+" consuming event "+e);
    992                         }
    993                         e.consume();
    994                     }
    995                 }
    996             }
    997         }
    998 
    999         @Override
    1000         public void keyTyped(KeyEvent e) {
    1001             // Not used
    1002         }
    1003 
    1004         @Override
    1005         public void keyReleased(KeyEvent e) {
    1006             // Not used
    1007         }
    1008     }
    1009 
    1010     private class SearchFieldTextListener implements DocumentListener {
    1011         private final JTextField searchField;
    1012         private final MainMenu mainMenu;
    1013         private String currentSearchText;
    1014 
    1015         SearchFieldTextListener(MainMenu mainMenu, JTextField searchField) {
    1016             this.mainMenu = mainMenu;
    1017             this.searchField = searchField;
    1018         }
    1019 
    1020         @Override
    1021         public void insertUpdate(DocumentEvent e) {
    1022             doSearch(searchField.getText());
    1023         }
    1024 
    1025         @Override
    1026         public void removeUpdate(DocumentEvent e) {
    1027             doSearch(searchField.getText());
    1028         }
    1029 
    1030         @Override
    1031         public void changedUpdate(DocumentEvent e) {
    1032             doSearch(searchField.getText());
    1033         }
    1034 
    1035         //TODO: perform some delay (maybe 200 ms) before actual searching.
    1036         void doSearch(String searchTerm) {
    1037             // Explicitely use default locale in this case, because we're looking for translated strings
    1038             searchTerm = searchTerm.trim().toLowerCase(Locale.getDefault());
    1039 
    1040             if (searchTerm.equals(currentSearchText)) {
    1041                 return;
    1042             }
    1043             currentSearchText = searchTerm;
    1044             if (searchTerm.isEmpty()) {
    1045                 // No text to search
    1046                 hideMenu();
    1047                 return;
    1048             }
    1049 
    1050             List<JMenuItem> searchResult = mainMenu.findMenuItems(currentSearchText);
    1051             if (searchResult.isEmpty()) {
    1052                 // Nothing found
    1053                 hideMenu();
    1054                 return;
    1055             }
    1056 
    1057             if (searchResult.size() > 20) {
    1058                 // Too many items found...
    1059                 searchResult = searchResult.subList(0, 20);
    1060             }
    1061 
    1062             // Update Popup menu
    1063             searchResultsMenu.removeAll();
    1064             for (JMenuItem foundItem : searchResult) {
    1065                 searchResultsMenu.add(foundItem.getText()).setAction(foundItem.getAction());
    1066             }
    1067             // Put menu right under search field
    1068             searchResultsMenu.pack();
    1069             searchResultsMenu.show(mainMenu, searchField.getX(), searchField.getY() + searchField.getHeight());
    1070 
    1071             // This is tricky. User still is able to edit search text. While Up and Down keys are handled by Popup Menu.
    1072             searchField.requestFocusInWindow();
    1073         }
    1074 
    1075         private void hideMenu() {
    1076             searchResultsMenu.setVisible(false);
    1077         }
    1078     }
    1079915}
  • 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 2c5162c..e430097 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  
    216175     * Constructs a new {@code TaggingPresetSelector}.
    217176     */
    218177    public TaggingPresetSelector(boolean displayOnlyApplicable, boolean displaySearchInTags) {
    219         super(new BorderLayout());
    220         classifications.loadPresets(TaggingPresets.getTaggingPresets());
    221 
    222         edSearchText = new JosmTextField();
    223         edSearchText.getDocument().addDocumentListener(new DocumentListener() {
    224             @Override
    225             public void removeUpdate(DocumentEvent e) {
    226                 filterPresets();
    227             }
    228 
    229             @Override
    230             public void insertUpdate(DocumentEvent e) {
    231                 filterPresets();
    232             }
    233 
    234             @Override
    235             public void changedUpdate(DocumentEvent e) {
    236                 filterPresets();
    237             }
    238         });
    239         edSearchText.addKeyListener(new KeyAdapter() {
    240             @Override
    241             public void keyPressed(KeyEvent e) {
    242                 switch (e.getKeyCode()) {
    243                 case KeyEvent.VK_DOWN:
    244                     selectPreset(lsResult.getSelectedIndex() + 1);
    245                     break;
    246                 case KeyEvent.VK_UP:
    247                     selectPreset(lsResult.getSelectedIndex() - 1);
    248                     break;
    249                 case KeyEvent.VK_PAGE_DOWN:
    250                     selectPreset(lsResult.getSelectedIndex() + 10);
    251                     break;
    252                 case KeyEvent.VK_PAGE_UP:
    253                     selectPreset(lsResult.getSelectedIndex() - 10);
    254                     break;
    255                 case KeyEvent.VK_HOME:
    256                     selectPreset(0);
    257                     break;
    258                 case KeyEvent.VK_END:
    259                     selectPreset(lsResultModel.getSize());
    260                     break;
    261                 }
    262             }
    263         });
    264         add(edSearchText, BorderLayout.NORTH);
    265 
    266         lsResult = new JList<>(lsResultModel);
     178        super();
    267179        lsResult.setCellRenderer(new ResultListCellRenderer());
    268         lsResult.addMouseListener(new MouseAdapter() {
    269             @Override
    270             public void mouseClicked(MouseEvent e) {
    271                 if (e.getClickCount() > 1) {
    272                     if (dblClickListener != null)
    273                         dblClickListener.actionPerformed(null);
    274                 } else {
    275                     if (clickListener != null)
    276                         clickListener.actionPerformed(null);
    277                 }
    278             }
    279         });
    280         add(new JScrollPane(lsResult), BorderLayout.CENTER);
     180        classifications.loadPresets(TaggingPresets.getTaggingPresets());
    281181
    282182        JPanel pnChecks = new JPanel();
    283183        pnChecks.setLayout(new BoxLayout(pnChecks, BoxLayout.Y_AXIS));
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    289189            ckOnlyApplicable.addItemListener(new ItemListener() {
    290190                @Override
    291191                public void itemStateChanged(ItemEvent e) {
    292                     filterPresets();
     192                    filterItems();
    293193                }
    294194            });
    295195        } else {
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    303203            ckSearchInTags.addItemListener(new ItemListener() {
    304204                @Override
    305205                public void itemStateChanged(ItemEvent e) {
    306                     filterPresets();
     206                    filterItems();
    307207                }
    308208            });
    309209            pnChecks.add(ckSearchInTags);
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    314214        add(pnChecks, BorderLayout.SOUTH);
    315215
    316216        setPreferredSize(new Dimension(400, 300));
    317         filterPresets();
     217        filterItems();
    318218        JPopupMenu popupMenu = new JPopupMenu();
    319219        popupMenu.add(new AbstractAction(tr("Add toolbar button")) {
    320220            @Override
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    326226        lsResult.addMouseListener(new PopupMenuLauncher(popupMenu));
    327227    }
    328228
    329     private synchronized void selectPreset(int newIndex) {
    330         if (newIndex < 0) {
    331             newIndex = 0;
    332         }
    333         if (newIndex > lsResultModel.getSize() - 1) {
    334             newIndex = lsResultModel.getSize() - 1;
    335         }
    336         lsResult.setSelectedIndex(newIndex);
    337         lsResult.ensureIndexIsVisible(newIndex);
    338     }
    339 
    340229    /**
    341230     * Search expression can be in form: "group1/group2/name" where names can contain multiple words
    342231     */
    343     private synchronized void filterPresets() {
     232    @Override
     233    protected synchronized void filterItems() {
    344234        //TODO Save favorites to file
    345235        String text = edSearchText.getText().toLowerCase(Locale.ENGLISH);
    346236        boolean onlyApplicable = ckOnlyApplicable != null && ckOnlyApplicable.isSelected();
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    352242                text, onlyApplicable, inTags, getTypesInSelection(), selected);
    353243
    354244        TaggingPreset oldPreset = getSelectedPreset();
    355         lsResultModel.setPresets(result);
     245        lsResultModel.setItems(Utils.transform(result, new Utils.Function<PresetClassification, TaggingPreset>() {
     246            @Override
     247            public TaggingPreset apply(PresetClassification x) {
     248                return x.preset;
     249            }
     250        }));
    356251        TaggingPreset newPreset = getSelectedPreset();
    357252        if (!Objects.equals(oldPreset, newPreset)) {
    358253            int[] indices = lsResult.getSelectedIndices();
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    486381        typesInSelectionDirty = true;
    487382    }
    488383
     384    @Override
    489385    public synchronized void init() {
    490386        if (ckOnlyApplicable != null) {
    491387            ckOnlyApplicable.setEnabled(!getTypesInSelection().isEmpty());
    492388            ckOnlyApplicable.setSelected(!getTypesInSelection().isEmpty() && ONLY_APPLICABLE.get());
    493389        }
    494         listSelectionListeners.clear();
    495         edSearchText.setText("");
    496         filterPresets();
     390        super.init();
    497391    }
    498392
    499393    public void init(Collection<TaggingPreset> presets) {
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    502396        init();
    503397    }
    504398
    505     public synchronized void clearSelection() {
    506         lsResult.getSelectionModel().clearSelection();
    507     }
    508 
    509399    /**
    510400     * Save checkbox values in preferences for future reuse
    511401     */
    public class TaggingPresetSelector extends JPanel implements SelectionChangedLis  
    542432    public synchronized void setSelectedPreset(TaggingPreset p) {
    543433        lsResult.setSelectedValue(p, true);
    544434    }
    545 
    546     public synchronized int getItemCount() {
    547         return lsResultModel.getSize();
    548     }
    549 
    550     public void setDblClickListener(ActionListener dblClickListener) {
    551         this.dblClickListener = dblClickListener;
    552     }
    553 
    554     public void setClickListener(ActionListener clickListener) {
    555         this.clickListener = clickListener;
    556     }
    557 
    558     /**
    559      * Adds a selection listener to the presets list.
    560      * @param selectListener The list selection listener
    561      * @since 7412
    562      */
    563     public synchronized void addSelectionListener(ListSelectionListener selectListener) {
    564         lsResult.getSelectionModel().addListSelectionListener(selectListener);
    565         listSelectionListeners.add(selectListener);
    566     }
    567 
    568     /**
    569      * Removes a selection listener from the presets list.
    570      * @param selectListener The list selection listener
    571      * @since 7412
    572      */
    573     public synchronized void removeSelectionListener(ListSelectionListener selectListener) {
    574         listSelectionListeners.remove(selectListener);
    575         lsResult.getSelectionModel().removeListSelectionListener(selectListener);
    576     }
    577435}
  • 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}