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