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