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