001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.Dimension;
008import java.awt.event.ActionEvent;
009import java.awt.event.KeyEvent;
010import java.util.Optional;
011
012import javax.swing.JList;
013import javax.swing.JMenuItem;
014import javax.swing.ListCellRenderer;
015
016import org.openstreetmap.josm.Main;
017import org.openstreetmap.josm.actions.JosmAction;
018import org.openstreetmap.josm.gui.ExtendedDialog;
019import org.openstreetmap.josm.gui.MainApplication;
020import org.openstreetmap.josm.gui.MainMenu;
021import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel;
022import org.openstreetmap.josm.tools.Shortcut;
023
024/**
025 * A dialog that allows you to search for a menu item. The user can input part of the menu item name.
026 */
027public final class MenuItemSearchDialog extends ExtendedDialog {
028
029    private final MenuItemSelector selector;
030    private static final MenuItemSearchDialog INSTANCE = new MenuItemSearchDialog(MainApplication.getMenu());
031
032    private MenuItemSearchDialog(MainMenu menu) {
033        super(Main.parent, tr("Search menu items"), tr("Select"), tr("Cancel"));
034        this.selector = new MenuItemSelector(menu);
035        this.selector.setDblClickListener(e -> buttonAction(0, null));
036        setContent(selector, false);
037        setPreferredSize(new Dimension(600, 300));
038    }
039
040    /**
041     * Returns the unique instance of {@code MenuItemSearchDialog}.
042     *
043     * @return the unique instance of {@code MenuItemSearchDialog}.
044     */
045    public static synchronized MenuItemSearchDialog getInstance() {
046        return INSTANCE;
047    }
048
049    @Override
050    public ExtendedDialog showDialog() {
051        selector.init();
052        super.showDialog();
053        selector.clearSelection();
054        return this;
055    }
056
057    @Override
058    protected void buttonAction(int buttonIndex, ActionEvent evt) {
059        super.buttonAction(buttonIndex, evt);
060        if (buttonIndex == 0 && selector.getSelectedItem() != null && selector.getSelectedItem().isEnabled()) {
061            selector.getSelectedItem().getAction().actionPerformed(evt);
062        }
063    }
064
065    private static class MenuItemSelector extends SearchTextResultListPanel<JMenuItem> {
066
067        private final MainMenu menu;
068
069        MenuItemSelector(MainMenu menu) {
070            super();
071            this.menu = menu;
072            lsResult.setCellRenderer(new CellRenderer());
073        }
074
075        public JMenuItem getSelectedItem() {
076            final JMenuItem selected = lsResult.getSelectedValue();
077            if (selected != null) {
078                return selected;
079            } else if (!lsResultModel.isEmpty()) {
080                return lsResultModel.getElementAt(0);
081            } else {
082                return null;
083            }
084        }
085
086        @Override
087        protected void filterItems() {
088            lsResultModel.setItems(menu.findMenuItems(edSearchText.getText(), true));
089        }
090    }
091
092    private static class CellRenderer implements ListCellRenderer<JMenuItem> {
093
094        @Override
095        public Component getListCellRendererComponent(JList<? extends JMenuItem> list, JMenuItem value, int index,
096                                                      boolean isSelected, boolean cellHasFocus) {
097            final JMenuItem item = new JMenuItem(value.getText());
098            item.setAction(value.getAction());
099            Optional.ofNullable(value.getAction())
100                    .filter(JosmAction.class::isInstance)
101                    .map(JosmAction.class::cast)
102                    .map(JosmAction::getShortcut)
103                    .map(Shortcut::getKeyStroke)
104                    .ifPresent(item::setAccelerator);
105            item.setArmed(isSelected);
106            if (isSelected) {
107                item.setBackground(list.getSelectionBackground());
108                item.setForeground(list.getSelectionForeground());
109            } else {
110                item.setBackground(list.getBackground());
111                item.setForeground(list.getForeground());
112            }
113            return item;
114        }
115    }
116
117    /**
118     * The action that opens the menu item search dialog.
119     */
120    public static class Action extends JosmAction {
121
122        // CHECKSTYLE.OFF: LineLength
123        /** Action shortcut (ctrl / space by default), made public in order to be used from {@code GettingStarted} page. */
124        public static final Shortcut SHORTCUT = Shortcut.registerShortcut("help:search-items", "Search menu items", KeyEvent.VK_SPACE, Shortcut.CTRL);
125        // CHECKSTYLE.ON: LineLength
126
127        /**
128         * Constructs a new {@code Action}.
129         */
130        public Action() {
131            super(tr("Search menu items"), "dialogs/search", null,
132                    SHORTCUT,
133                    true, "dialogs/search-items", false);
134        }
135
136        @Override
137        public void actionPerformed(ActionEvent e) {
138            MenuItemSearchDialog.getInstance().showDialog();
139        }
140    }
141}