Ticket #10882: MainMenu.java.patch

File MainMenu.java.patch, 8.5 KB (added by strump, 11 years ago)

SVN Patch of this feature

  • MainMenu.java

     
    66import static org.openstreetmap.josm.tools.I18n.tr;
    77
    88import java.awt.Component;
     9import java.awt.Dimension;
    910import java.awt.GraphicsEnvironment;
     11import java.awt.event.ActionEvent;
    1012import java.awt.event.KeyEvent;
     13import java.awt.event.KeyListener;
    1114import java.util.HashMap;
     15import java.util.LinkedList;
     16import java.util.List;
    1217import java.util.Map;
    1318
     19import javax.swing.Action;
    1420import javax.swing.JCheckBoxMenuItem;
     21import javax.swing.JComponent;
    1522import javax.swing.JMenu;
    1623import javax.swing.JMenuBar;
    1724import javax.swing.JMenuItem;
    1825import javax.swing.JPopupMenu;
    1926import javax.swing.JSeparator;
     27import javax.swing.JTextField;
     28import javax.swing.JTextPane;
    2029import javax.swing.KeyStroke;
     30import javax.swing.MenuElement;
     31import javax.swing.MenuSelectionManager;
     32import javax.swing.event.DocumentEvent;
     33import javax.swing.event.DocumentListener;
    2134import javax.swing.event.MenuEvent;
    2235import javax.swing.event.MenuListener;
    2336
     
    384397    public final DialogsToggleAction dialogsToggleAction = new DialogsToggleAction();
    385398    public FullscreenToggleAction fullscreenToggleAction = null;
    386399
     400    /**
     401     * Popup menu to display menu items search result.
     402     */
     403    private JPopupMenu searchResultsMenu = new JPopupMenu();
     404
    387405    /** this menu listener hides unnecessary JSeparators in a menu list but does not remove them.
    388406     * If at a later time the separators are required, they will be made visible again. Intended
    389407     * usage is make menus not look broken if separators are used to group the menu and some of
     
    772790        current.setAccelerator(Shortcut.registerShortcut("system:help", tr("Help"), KeyEvent.VK_F1,
    773791                Shortcut.DIRECT).getKeyStroke());
    774792        add(helpMenu, about);
     793        add(createSpacer(50));
     794        add(createSearchField());
    775795
    776 
    777796        windowMenu.addMenuListener(menuSeparatorHandler);
    778797
    779798        new PresetsMenuEnabler(presetsMenu).refreshEnabled();
    780799    }
    781800
     801    /**
     802     * Create spacer: empty component with fixed width.
     803     */
     804    private JComponent createSpacer(int pixels) {
     805        JTextPane spacer = new JTextPane();
     806        spacer.setMinimumSize(new Dimension(pixels, 0));
     807        spacer.setMaximumSize(new Dimension(pixels, 0));
     808        return spacer;
     809    }
     810
     811    /**
     812     * Create search field.
     813     */
     814    private JComponent createSearchField() {
     815        JTextField searchField = new JTextField();
     816        searchField.setEditable(true);
     817        searchField.setMaximumSize(new Dimension(200, 50));
     818        searchField.setToolTipText(marktr("Search menu items"));
     819        searchField.addKeyListener(new SearchFieldKeyListener());
     820        searchField.getDocument().addDocumentListener(new SearchFieldTextListener(this, searchField));
     821        return searchField;
     822    }
     823
     824    /**
     825     * Search main menu for items with {@param textToFind} in title.
     826     * @param textToFind
     827     * @return not null list of found menu items.
     828     */
     829    private List<JMenuItem> findMenuItems(String textToFind) {
     830        textToFind = textToFind.toLowerCase();
     831        LinkedList<JMenuItem> result = new LinkedList<>();
     832        int menuCount = getMenuCount();
     833
     834        //Iterate over main menus
     835        for(MenuElement menuElement : getSubElements()) {
     836            if( !(menuElement instanceof JMenu) ) continue;
     837
     838            JMenu mainMenuItem = (JMenu) menuElement;
     839            if(mainMenuItem.getAction()!=null && mainMenuItem.getText().toLowerCase().contains(textToFind)) {
     840                result.add(mainMenuItem);
     841            }
     842
     843            //Search recursively
     844            findMenuItems(mainMenuItem, textToFind, result);
     845        }
     846        return result;
     847    }
     848
     849    /**
     850     * Recursive walker for menu items. Only menu items with action are selected. If menu item
     851     * contains {@param textToFind} it's appended to result.
     852     * @param menu
     853     * @param textToFind
     854     * @param result
     855     */
     856    private void findMenuItems(final JMenu menu, final String textToFind, final List<JMenuItem> result) {
     857        int count = menu.getItemCount();
     858        for(int i=0; i<count; i++) {
     859            JMenuItem menuItem = menu.getItem(i);
     860            if(menuItem == null) continue;
     861
     862            if (menuItem.getAction()!=null && menuItem.getText().toLowerCase().contains(textToFind)) {
     863                result.add(menuItem);
     864            }
     865
     866            //Go recursive if needed
     867            if(menuItem instanceof JMenu) {
     868                findMenuItems((JMenu) menuItem, textToFind, result);
     869            }
     870        }
     871    }
     872
    782873    protected void showAudioMenu(boolean showMenu) {
    783874        if (showMenu && audioMenu == null) {
    784875            audioMenu = addMenu(marktr("Audio"), KeyEvent.VK_U, defaultMenuPos, ht("/Menu/Audio"));
     
    826917            refreshEnabled();
    827918        }
    828919    }
     920
     921    /**
     922     * This listener is designed to handle ENTER key pressed in menu search field.
     923     * When user presses Enter key then selected item of "searchResultsMenu" is triggered.
     924     */
     925    class SearchFieldKeyListener implements KeyListener {
     926        public SearchFieldKeyListener() {
     927            super();
     928        }
     929
     930        @Override
     931        public void keyPressed(KeyEvent e) {
     932            if(e.getKeyCode() == KeyEvent.VK_ENTER) {
     933                //On ENTER selected menu item must be triggered
     934                MenuElement[] selection = MenuSelectionManager.defaultManager().getSelectedPath();
     935                if(selection.length > 1) {
     936                    MenuElement selectedElement = selection[selection.length-1];
     937                    if(selectedElement instanceof JMenuItem) {
     938                        JMenuItem selectedItem = (JMenuItem) selectedElement;
     939                        Action menuAction = selectedItem.getAction();
     940                        menuAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null));
     941                        e.consume();
     942                    }
     943                }
     944            }
     945        }
     946
     947        @Override
     948        public void keyTyped(KeyEvent e) { }
     949
     950        @Override
     951        public void keyReleased(KeyEvent e) { }
     952    }
     953
     954    class SearchFieldTextListener implements DocumentListener {
     955        private final JTextField searchField;
     956        private final MainMenu mainMenu;
     957        private String currentSearchText = null;
     958
     959        public SearchFieldTextListener(MainMenu mainMenu, JTextField searchField) {
     960            this.mainMenu = mainMenu;
     961            this.searchField = searchField;
     962        }
     963
     964        @Override
     965        public void insertUpdate(DocumentEvent e) {
     966            doSearch(searchField.getText());
     967        }
     968
     969        @Override
     970        public void removeUpdate(DocumentEvent e) {
     971            doSearch(searchField.getText());
     972        }
     973
     974        @Override
     975        public void changedUpdate(DocumentEvent e) {
     976            doSearch(searchField.getText());
     977        }
     978
     979        //TODO: perform some delay (maybe 200 ms) before actual searching.
     980        void doSearch(String searchTerm) {
     981            searchTerm = searchTerm.trim().toLowerCase();
     982
     983            if (searchTerm.equals(currentSearchText)) {
     984                return;
     985            }
     986            currentSearchText = searchTerm;
     987            if (searchTerm.length() == 0) {
     988                //No text to search
     989                hideMenu();
     990                return;
     991            }
     992
     993            List<JMenuItem> searchResult = mainMenu.findMenuItems(currentSearchText);
     994            if(searchResult.size() == 0) {
     995                //Nothing found
     996                hideMenu();
     997                return;
     998            }
     999
     1000            if(searchResult.size() > 20) {
     1001                //Too many items found...
     1002                searchResult = searchResult.subList(0, 20);
     1003            }
     1004
     1005            //Update Popup menu
     1006            searchResultsMenu.removeAll();
     1007            for (JMenuItem foundItem : searchResult) {
     1008                searchResultsMenu.add(foundItem.getText()).setAction(foundItem.getAction());
     1009            }
     1010            searchResultsMenu.pack();
     1011            searchResultsMenu.show(mainMenu, searchField.getX(), searchField.getY() + searchField.getHeight()); //Put menu right under search field
     1012
     1013            searchField.grabFocus(); //This is tricky. User still is able to edit search text. While Up and Down keys are handled by Popup Menu.
     1014        }
     1015
     1016        private void hideMenu() {
     1017            searchResultsMenu.setVisible(false);
     1018        }
     1019
     1020    }
    8291021}