Changeset 8072 in josm


Ignore:
Timestamp:
2015-02-15T22:15:48+01:00 (9 years ago)
Author:
Don-vip
Message:

fix #10882 - Implement search box for main menu (modified patch from strump)

Location:
trunk/src/org/openstreetmap/josm
Files:
5 edited

Legend:

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

    r8020 r8072  
    221221     * The MOTD Layer.
    222222     */
    223     private GettingStarted gettingStarted = new GettingStarted();
     223    public final GettingStarted gettingStarted = new GettingStarted();
    224224
    225225    private static final Collection<MapFrameListener> mapFrameListeners = new ArrayList<>();
  • trunk/src/org/openstreetmap/josm/gui/MainApplication.java

    r8061 r8072  
    442442                splash.dispose();
    443443                mainFrame.setVisible(true);
     444                main.gettingStarted.requestFocusInWindow();
    444445            }
    445446        });
  • trunk/src/org/openstreetmap/josm/gui/MainMenu.java

    r8071 r8072  
    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;
     14import java.util.ArrayList;
    1115import java.util.HashMap;
     16import java.util.List;
    1217import java.util.Map;
    1318
     19import javax.swing.Action;
     20import javax.swing.Box;
    1421import javax.swing.JCheckBoxMenuItem;
     22import javax.swing.JComponent;
    1523import javax.swing.JMenu;
    1624import javax.swing.JMenuBar;
     
    1826import javax.swing.JPopupMenu;
    1927import javax.swing.JSeparator;
     28import javax.swing.JTextField;
    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;
     
    117130import org.openstreetmap.josm.gui.tagging.TaggingPresetSearchAction;
    118131import org.openstreetmap.josm.gui.tagging.TaggingPresetSearchPrimitiveDialog;
     132import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField;
    119133import org.openstreetmap.josm.tools.ImageProvider;
    120134import org.openstreetmap.josm.tools.Shortcut;
     
    388402    public FullscreenToggleAction fullscreenToggleAction = null;
    389403
     404    /**
     405     * Popup menu to display menu items search result.
     406     */
     407    private JPopupMenu searchResultsMenu = new JPopupMenu();
     408
    390409    /** this menu listener hides unnecessary JSeparators in a menu list but does not remove them.
    391410     * If at a later time the separators are required, they will be made visible again. Intended
     
    777796                Shortcut.DIRECT).getKeyStroke());
    778797        add(helpMenu, about);
    779 
     798        add(Box.createHorizontalGlue());
     799        add(createSearchField());
    780800
    781801        windowMenu.addMenuListener(menuSeparatorHandler);
    782802
    783803        new PresetsMenuEnabler(presetsMenu).refreshEnabled();
     804    }
     805
     806    /**
     807     * Create search field.
     808     */
     809    private JComponent createSearchField() {
     810        DisableShortcutsOnFocusGainedTextField searchField = new DisableShortcutsOnFocusGainedTextField();
     811        searchField.setEditable(true);
     812        Dimension d = new Dimension(200, helpMenu.getPreferredSize().height);
     813        searchField.setPreferredSize(d);
     814        searchField.setMaximumSize(d);
     815        searchField.setHint(tr("Search menu items"));
     816        searchField.setToolTipText(tr("Search menu items"));
     817        searchField.addKeyListener(new SearchFieldKeyListener());
     818        searchField.getDocument().addDocumentListener(new SearchFieldTextListener(this, searchField));
     819        return searchField;
     820    }
     821
     822    /**
     823     * Search main menu for items with {@code textToFind} in title.
     824     * @param textToFind The text to find
     825     * @return not null list of found menu items.
     826     */
     827    private List<JMenuItem> findMenuItems(String textToFind) {
     828        textToFind = textToFind.toLowerCase();
     829        List<JMenuItem> result = new ArrayList<>();
     830
     831        // Iterate over main menus
     832        for (MenuElement menuElement : getSubElements()) {
     833            if( !(menuElement instanceof JMenu)) continue;
     834
     835            JMenu mainMenuItem = (JMenu) menuElement;
     836            if(mainMenuItem.getAction()!=null && mainMenuItem.getText().toLowerCase().contains(textToFind)) {
     837                result.add(mainMenuItem);
     838            }
     839
     840            //Search recursively
     841            findMenuItems(mainMenuItem, textToFind, result);
     842        }
     843        return result;
     844    }
     845
     846    /**
     847     * Recursive walker for menu items. Only menu items with action are selected. If menu item
     848     * contains {@code textToFind} it's appended to result.
     849     * @param menu menu in which search will be performed
     850     * @param textToFind The text to find
     851     * @param result resulting list ofmenu items
     852     */
     853    private void findMenuItems(final JMenu menu, final String textToFind, final List<JMenuItem> result) {
     854        for (int i=0; i<menu.getItemCount(); i++) {
     855            JMenuItem menuItem = menu.getItem(i);
     856            if (menuItem == null) continue;
     857
     858            if (menuItem.getAction()!=null && menuItem.getText().toLowerCase().contains(textToFind)) {
     859                result.add(menuItem);
     860            }
     861
     862            // Go recursive if needed
     863            if (menuItem instanceof JMenu) {
     864                findMenuItems((JMenu) menuItem, textToFind, result);
     865            }
     866        }
    784867    }
    785868
     
    831914        }
    832915    }
     916
     917    /**
     918     * This listener is designed to handle ENTER key pressed in menu search field.
     919     * When user presses Enter key then selected item of "searchResultsMenu" is triggered.
     920     */
     921    private class SearchFieldKeyListener implements KeyListener {
     922
     923        @Override
     924        public void keyPressed(KeyEvent e) {
     925            if (e.getKeyCode() == KeyEvent.VK_ENTER) {
     926                // On ENTER selected menu item must be triggered
     927                MenuElement[] selection = MenuSelectionManager.defaultManager().getSelectedPath();
     928                if(selection.length > 1) {
     929                    MenuElement selectedElement = selection[selection.length-1];
     930                    if(selectedElement instanceof JMenuItem) {
     931                        JMenuItem selectedItem = (JMenuItem) selectedElement;
     932                        Action menuAction = selectedItem.getAction();
     933                        menuAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null));
     934                        e.consume();
     935                    }
     936                }
     937            }
     938        }
     939
     940        @Override
     941        public void keyTyped(KeyEvent e) { }
     942
     943        @Override
     944        public void keyReleased(KeyEvent e) { }
     945    }
     946
     947    private class SearchFieldTextListener implements DocumentListener {
     948        private final JTextField searchField;
     949        private final MainMenu mainMenu;
     950        private String currentSearchText = null;
     951
     952        public SearchFieldTextListener(MainMenu mainMenu, JTextField searchField) {
     953            this.mainMenu = mainMenu;
     954            this.searchField = searchField;
     955        }
     956
     957        @Override
     958        public void insertUpdate(DocumentEvent e) {
     959            doSearch(searchField.getText());
     960        }
     961
     962        @Override
     963        public void removeUpdate(DocumentEvent e) {
     964            doSearch(searchField.getText());
     965        }
     966
     967        @Override
     968        public void changedUpdate(DocumentEvent e) {
     969            doSearch(searchField.getText());
     970        }
     971
     972        //TODO: perform some delay (maybe 200 ms) before actual searching.
     973        void doSearch(String searchTerm) {
     974            searchTerm = searchTerm.trim().toLowerCase();
     975
     976            if (searchTerm.equals(currentSearchText)) {
     977                return;
     978            }
     979            currentSearchText = searchTerm;
     980            if (searchTerm.length() == 0) {
     981                //No text to search
     982                hideMenu();
     983                return;
     984            }
     985
     986            List<JMenuItem> searchResult = mainMenu.findMenuItems(currentSearchText);
     987            if(searchResult.size() == 0) {
     988                //Nothing found
     989                hideMenu();
     990                return;
     991            }
     992
     993            if(searchResult.size() > 20) {
     994                //Too many items found...
     995                searchResult = searchResult.subList(0, 20);
     996            }
     997
     998            //Update Popup menu
     999            searchResultsMenu.removeAll();
     1000            for (JMenuItem foundItem : searchResult) {
     1001                searchResultsMenu.add(foundItem.getText()).setAction(foundItem.getAction());
     1002            }
     1003            searchResultsMenu.pack();
     1004            searchResultsMenu.show(mainMenu, searchField.getX(), searchField.getY() + searchField.getHeight()); //Put menu right under search field
     1005
     1006            searchField.grabFocus(); //This is tricky. User still is able to edit search text. While Up and Down keys are handled by Popup Menu.
     1007        }
     1008
     1009        private void hideMenu() {
     1010            searchResultsMenu.setVisible(false);
     1011        }
     1012
     1013    }
    8331014}
  • trunk/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java

    r7668 r8072  
    587587                            dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
    588588                            hideNotify();
     589                            if (Main.map != null) {
     590                                // To avoid menu bar search field from taking focus when a toggle dialog is closed
     591                                Main.map.requestFocusInWindow();
     592                            }
    589593                        }
    590594                    }
  • trunk/src/org/openstreetmap/josm/gui/util/AdvancedKeyPressDetector.java

    r7937 r8072  
    2323
    2424/**
    25  * Helper object that allows cross-platform detection of key press and realease events
    26  * instance is available globally as Main.map.keyDetector
     25 * Helper object that allows cross-platform detection of key press and release events
     26 * instance is available globally as {@code Main.map.keyDetector}.
    2727 * @since 7217
    2828 */
Note: See TracChangeset for help on using the changeset viewer.