Ignore:
Timestamp:
2016-01-09T12:00:51+01:00 (8 years ago)
Author:
simon04
Message:

fix #12224 - Dialog for "Search menu items"

This places the "Search menu items" functionality in the help menu
instead of placing a text field at the very right of the main menu.

Location:
trunk/src/org/openstreetmap/josm/gui
Files:
2 added
3 edited

Legend:

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

    r9238 r9347  
    473473                splash.dispose();
    474474                mainFrame.setVisible(true);
    475                 main.gettingStarted.requestFocusInWindow();
    476475            }
    477476        });
  • trunk/src/org/openstreetmap/josm/gui/MainMenu.java

    r9320 r9347  
    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;
     
    1915import java.util.Map;
    2016
    21 import javax.swing.Action;
    22 import javax.swing.Box;
    2317import javax.swing.JCheckBoxMenuItem;
    2418import javax.swing.JMenu;
     
    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;
     
    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;
     
    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
     
    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
     
    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     */
     
    818802        });
    819803
     804        add(helpMenu, new MenuItemSearchDialog.Action());
     805        helpMenu.addSeparator();
    820806        add(helpMenu, statusreport);
    821807        add(helpMenu, reportbug);
     
    824810        add(helpMenu, help);
    825811        add(helpMenu, about);
    826         add(Box.createHorizontalGlue());
    827         final DisableShortcutsOnFocusGainedTextField searchField = createSearchField();
    828         add(searchField);
    829 
    830         // Do not let search field take the focus automatically
    831         setFocusTraversalPolicyProvider(true);
    832         setFocusTraversalPolicy(new DefaultFocusTraversalPolicy() {
    833             @Override
    834             protected boolean accept(Component aComponent) {
    835                 return super.accept(aComponent) && !searchField.equals(aComponent);
    836             }
    837         });
    838812
    839813        windowMenu.addMenuListener(menuSeparatorHandler);
     
    854828
    855829    /**
    856      * Create search field.
    857      * @return the search field
    858      */
    859     private DisableShortcutsOnFocusGainedTextField createSearchField() {
    860         DisableShortcutsOnFocusGainedTextField searchField = new DisableShortcutsOnFocusGainedTextField() {
    861             @Override
    862             public Dimension getPreferredSize() {
    863                 // JMenuBar uses a BoxLayout and it doesn't seem possible to specify a size factor,
    864                 // so compute the preferred size dynamically
    865                 return new Dimension(Math.min(200, Math.max(25, getMaximumAvailableWidth())),
    866                         helpMenu.getPreferredSize().height);
    867             }
    868         };
    869         Shortcut searchFieldShortcut = Shortcut.registerShortcut("menu:search-field", tr("Search menu items"), KeyEvent.VK_R, Shortcut.MNEMONIC);
    870         searchFieldShortcut.setFocusAccelerator(searchField);
    871         searchField.setEditable(true);
    872         searchField.setMaximumSize(new Dimension(200, helpMenu.getPreferredSize().height));
    873         searchField.setHint(tr("Search menu items"));
    874         searchField.setToolTipText(Main.platform.makeTooltip(tr("Search menu items"), searchFieldShortcut));
    875         searchField.addKeyListener(new SearchFieldKeyListener());
    876         searchField.getDocument().addDocumentListener(new SearchFieldTextListener(this, searchField));
    877         return searchField;
    878     }
    879 
    880     /**
    881830     * Search main menu for items with {@code textToFind} in title.
    882831     * @param textToFind The text to find
     832     * @param skipPresets whether to skip the {@link #presetsMenu} in the search
    883833     * @return not null list of found menu items.
    884834     */
    885     private List<JMenuItem> findMenuItems(String textToFind) {
    886         // Explicitely use default locale in this case, because we're looking for translated strings
     835    public List<JMenuItem> findMenuItems(String textToFind, boolean skipPresets) {
     836        // Explicitly use default locale in this case, because we're looking for translated strings
    887837        textToFind = textToFind.toLowerCase(Locale.getDefault());
    888838        List<JMenuItem> result = new ArrayList<>();
    889 
    890         // Iterate over main menus
    891         for (MenuElement menuElement : getSubElements()) {
    892             if (!(menuElement instanceof JMenu)) continue;
    893 
    894             JMenu mainMenuItem = (JMenu) menuElement;
    895             if (mainMenuItem.getAction() != null && mainMenuItem.getText().toLowerCase(Locale.getDefault()).contains(textToFind)) {
    896                 result.add(mainMenuItem);
     839        for (int i = 0; i < getMenuCount(); i++) {
     840            if (getMenu(i) != null && (!skipPresets || presetsMenu != getMenu(i))) {
     841                findMenuItems(getMenu(i), textToFind, result);
    897842            }
    898 
    899             //Search recursively
    900             findMenuItems(mainMenuItem, textToFind, result);
    901843        }
    902844        return result;
     
    908850     * @param menu menu in which search will be performed
    909851     * @param textToFind The text to find
    910      * @param result resulting list ofmenu items
     852     * @param result resulting list of menu items
    911853     */
    912854    private static void findMenuItems(final JMenu menu, final String textToFind, final List<JMenuItem> result) {
     
    915857            if (menuItem == null) continue;
    916858
    917             // Explicitely use default locale in this case, because we're looking for translated strings
     859            // Explicitly use default locale in this case, because we're looking for translated strings
    918860            if (menuItem.getAction() != null && menuItem.getText().toLowerCase(Locale.getDefault()).contains(textToFind)) {
    919861                result.add(menuItem);
     
    977919    }
    978920
    979     /**
    980      * This listener is designed to handle ENTER key pressed in menu search field.
    981      * When user presses Enter key then selected item of "searchResultsMenu" is triggered.
    982      */
    983     private static class SearchFieldKeyListener implements KeyListener {
    984 
    985         @Override
    986         public void keyPressed(KeyEvent e) {
    987             if (e.getKeyCode() == KeyEvent.VK_ENTER) {
    988                 // On ENTER selected menu item must be triggered
    989                 MenuElement[] selection = MenuSelectionManager.defaultManager().getSelectedPath();
    990                 if (selection.length > 1) {
    991                     MenuElement selectedElement = selection[selection.length-1];
    992                     if (selectedElement instanceof JMenuItem) {
    993                         JMenuItem selectedItem = (JMenuItem) selectedElement;
    994                         Action menuAction = selectedItem.getAction();
    995                         menuAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null));
    996                         if (Main.isDebugEnabled()) {
    997                             Main.debug(getClass().getName()+" consuming event "+e);
    998                         }
    999                         e.consume();
    1000                     }
    1001                 }
    1002             }
    1003         }
    1004 
    1005         @Override
    1006         public void keyTyped(KeyEvent e) {
    1007             // Not used
    1008         }
    1009 
    1010         @Override
    1011         public void keyReleased(KeyEvent e) {
    1012             // Not used
    1013         }
    1014     }
    1015 
    1016     private class SearchFieldTextListener implements DocumentListener {
    1017         private final JTextField searchField;
    1018         private final MainMenu mainMenu;
    1019         private String currentSearchText;
    1020 
    1021         SearchFieldTextListener(MainMenu mainMenu, JTextField searchField) {
    1022             this.mainMenu = mainMenu;
    1023             this.searchField = searchField;
    1024         }
    1025 
    1026         @Override
    1027         public void insertUpdate(DocumentEvent e) {
    1028             doSearch(searchField.getText());
    1029         }
    1030 
    1031         @Override
    1032         public void removeUpdate(DocumentEvent e) {
    1033             doSearch(searchField.getText());
    1034         }
    1035 
    1036         @Override
    1037         public void changedUpdate(DocumentEvent e) {
    1038             doSearch(searchField.getText());
    1039         }
    1040 
    1041         //TODO: perform some delay (maybe 200 ms) before actual searching.
    1042         void doSearch(String searchTerm) {
    1043             // Explicitely use default locale in this case, because we're looking for translated strings
    1044             searchTerm = searchTerm.trim().toLowerCase(Locale.getDefault());
    1045 
    1046             if (searchTerm.equals(currentSearchText)) {
    1047                 return;
    1048             }
    1049             currentSearchText = searchTerm;
    1050             if (searchTerm.isEmpty()) {
    1051                 // No text to search
    1052                 hideMenu();
    1053                 return;
    1054             }
    1055 
    1056             List<JMenuItem> searchResult = mainMenu.findMenuItems(currentSearchText);
    1057             if (searchResult.isEmpty()) {
    1058                 // Nothing found
    1059                 hideMenu();
    1060                 return;
    1061             }
    1062 
    1063             if (searchResult.size() > 20) {
    1064                 // Too many items found...
    1065                 searchResult = searchResult.subList(0, 20);
    1066             }
    1067 
    1068             // Update Popup menu
    1069             searchResultsMenu.removeAll();
    1070             for (JMenuItem foundItem : searchResult) {
    1071                 searchResultsMenu.add(foundItem.getText()).setAction(foundItem.getAction());
    1072             }
    1073             // Put menu right under search field
    1074             searchResultsMenu.pack();
    1075             searchResultsMenu.show(mainMenu, searchField.getX(), searchField.getY() + searchField.getHeight());
    1076 
    1077             // This is tricky. User still is able to edit search text. While Up and Down keys are handled by Popup Menu.
    1078             searchField.requestFocusInWindow();
    1079         }
    1080 
    1081         private void hideMenu() {
    1082             searchResultsMenu.setVisible(false);
    1083         }
    1084     }
    1085921}
  • trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java

    r9304 r9347  
    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;
     
    2722
    2823import javax.swing.AbstractAction;
    29 import javax.swing.AbstractListModel;
    3024import javax.swing.Action;
    3125import javax.swing.BoxLayout;
     
    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;
     
    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;
     
    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;
     
    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;
     
    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> {
     
    9679            result.setIcon((Icon) tp.getValue(Action.SMALL_ICON));
    9780            return result;
    98         }
    99     }
    100 
    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();
    12281        }
    12382    }
     
    219178     */
    220179    public TaggingPresetSelector(boolean displayOnlyApplicable, boolean displaySearchInTags) {
    221         super(new BorderLayout());
     180        super();
     181        lsResult.setCellRenderer(new ResultListCellRenderer());
    222182        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);
    269         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);
    283183
    284184        JPanel pnChecks = new JPanel();
     
    292192                @Override
    293193                public void itemStateChanged(ItemEvent e) {
    294                     filterPresets();
     194                    filterItems();
    295195                }
    296196            });
     
    306206                @Override
    307207                public void itemStateChanged(ItemEvent e) {
    308                     filterPresets();
     208                    filterItems();
    309209                }
    310210            });
     
    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")) {
     
    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);
     
    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)) {
     
    491386    }
    492387
     388    @Override
    493389    public synchronized void init() {
    494390        if (ckOnlyApplicable != null) {
     
    496392            ckOnlyApplicable.setSelected(!getTypesInSelection().isEmpty() && ONLY_APPLICABLE.get());
    497393        }
    498         listSelectionListeners.clear();
    499         edSearchText.setText("");
    500         filterPresets();
     394        super.init();
    501395    }
    502396
     
    505399        classifications.loadPresets(presets);
    506400        init();
    507     }
    508 
    509     public synchronized void clearSelection() {
    510         lsResult.getSelectionModel().clearSelection();
    511401    }
    512402
     
    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}
Note: See TracChangeset for help on using the changeset viewer.