001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.tagging.presets;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.MouseInfo;
008import java.awt.Point;
009import java.awt.PointerInfo;
010import java.awt.event.ActionEvent;
011import java.io.Serializable;
012import java.util.ArrayList;
013import java.util.Comparator;
014import java.util.List;
015import java.util.Objects;
016
017import javax.swing.Action;
018import javax.swing.JMenu;
019import javax.swing.JMenuItem;
020import javax.swing.JPopupMenu;
021import javax.swing.JSeparator;
022
023import org.openstreetmap.josm.Main;
024import org.openstreetmap.josm.tools.AlphanumComparator;
025
026public class TaggingPresetMenu extends TaggingPreset {
027    public JMenu menu; // set by TaggingPresets
028
029    private static class PresetTextComparator implements Comparator<JMenuItem>, Serializable {
030        @Override
031        public int compare(JMenuItem o1, JMenuItem o2) {
032            if (Main.main.menu.presetSearchAction.equals(o1.getAction()))
033                return -1;
034            else if (Main.main.menu.presetSearchAction.equals(o2.getAction()))
035                return 1;
036            else
037                return AlphanumComparator.getInstance().compare(o1.getText(), o2.getText());
038        }
039    }
040
041    /**
042     * {@code TaggingPresetMenu} are considered equivalent if (and only if) their {@link #getRawName()} match.
043     */
044    @Override
045    public boolean equals(Object o) {
046        if (this == o) return true;
047        if (o == null || getClass() != o.getClass()) return false;
048        TaggingPresetMenu that = (TaggingPresetMenu) o;
049        return Objects.equals(getRawName(), that.getRawName());
050    }
051
052    @Override
053    public int hashCode() {
054        return Objects.hash(getRawName());
055    }
056
057    @Override
058    public void setDisplayName() {
059        putValue(Action.NAME, getName());
060        /** Tooltips should be shown for the toolbar buttons, but not in the menu. */
061        putValue(OPTIONAL_TOOLTIP_TEXT, group != null ?
062                tr("Preset group {1} / {0}", getLocaleName(), group.getName()) :
063                    tr("Preset group {0}", getLocaleName()));
064        putValue("toolbar", "tagginggroup_" + getRawName());
065    }
066
067    private static Component copyMenuComponent(Component menuComponent) {
068        if (menuComponent instanceof JMenu) {
069            JMenu menu = (JMenu) menuComponent;
070            JMenu result = new JMenu(menu.getAction());
071            for (Component item:menu.getMenuComponents()) {
072                result.add(copyMenuComponent(item));
073            }
074            result.setText(menu.getText());
075            return result;
076        } else if (menuComponent instanceof JMenuItem) {
077            JMenuItem menuItem = (JMenuItem) menuComponent;
078            JMenuItem result = new JMenuItem(menuItem.getAction());
079            result.setText(menuItem.getText());
080            return result;
081        } else if (menuComponent instanceof JSeparator) {
082            return new JSeparator();
083        } else {
084            return menuComponent;
085        }
086    }
087
088    @Override
089    public void actionPerformed(ActionEvent e) {
090        Object s = e.getSource();
091        if (menu != null && s instanceof Component) {
092            JPopupMenu pm = new JPopupMenu(getName());
093            for (Component c : menu.getMenuComponents()) {
094                pm.add(copyMenuComponent(c));
095            }
096            PointerInfo pointerInfo = MouseInfo.getPointerInfo();
097            if (pointerInfo != null) {
098                Point p = pointerInfo.getLocation();
099                pm.show(Main.parent, p.x-Main.parent.getX(), p.y-Main.parent.getY());
100            }
101        }
102    }
103
104    /**
105     * Sorts the menu items using the translated item text
106     */
107    public void sortMenu() {
108        TaggingPresetMenu.sortMenu(this.menu);
109    }
110
111    /**
112     * Sorts the menu items using the translated item text
113     * @param menu menu to sort
114     */
115    public static void sortMenu(JMenu menu) {
116        Component[] items = menu.getMenuComponents();
117        PresetTextComparator comp = new PresetTextComparator();
118        List<JMenuItem> sortarray = new ArrayList<>();
119        int lastSeparator = 0;
120        for (int i = 0; i < items.length; i++) {
121            Object item = items[i];
122            if (item instanceof JMenu) {
123                sortMenu((JMenu) item);
124            }
125            if (item instanceof JMenuItem) {
126                sortarray.add((JMenuItem) item);
127                if (i == items.length-1) {
128                    handleMenuItem(menu, comp, sortarray, lastSeparator);
129                    sortarray = new ArrayList<>();
130                    lastSeparator = 0;
131                }
132            } else if (item instanceof JSeparator) {
133                handleMenuItem(menu, comp, sortarray, lastSeparator);
134                sortarray = new ArrayList<>();
135                lastSeparator = i;
136            }
137        }
138    }
139
140    private static void handleMenuItem(JMenu menu, PresetTextComparator comp, List<JMenuItem> sortarray, int lastSeparator) {
141        sortarray.sort(comp);
142        int pos = 0;
143        for (JMenuItem menuItem : sortarray) {
144            int oldPos;
145            if (lastSeparator == 0) {
146                oldPos = pos;
147            } else {
148                oldPos = pos+lastSeparator+1;
149            }
150            menu.add(menuItem, oldPos);
151            pos++;
152        }
153    }
154}