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.gui.MainApplication;
024import org.openstreetmap.josm.gui.MainFrame;
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                    MainFrame parent = MainApplication.getMainFrame();
108                    if (parent.isShowing()) {
109                        pm.show(parent, p.x-parent.getX(), p.y-parent.getY());
110                    }
111                }
112            } catch (SecurityException ex) {
113                Logging.log(Logging.LEVEL_ERROR, "Unable to get mouse pointer info", ex);
114            }
115        }
116    }
117
118    /**
119     * Sorts the menu items using the translated item text
120     */
121    public void sortMenu() {
122        TaggingPresetMenu.sortMenu(this.menu);
123    }
124
125    /**
126     * Sorts the menu items using the translated item text
127     * @param menu menu to sort
128     */
129    public static void sortMenu(JMenu menu) {
130        Component[] items = menu.getMenuComponents();
131        PresetTextComparator comp = new PresetTextComparator();
132        List<JMenuItem> sortarray = new ArrayList<>();
133        int lastSeparator = 0;
134        for (int i = 0; i < items.length; i++) {
135            Object item = items[i];
136            if (item instanceof JMenu) {
137                sortMenu((JMenu) item);
138            }
139            if (item instanceof JMenuItem) {
140                sortarray.add((JMenuItem) item);
141                if (i == items.length-1) {
142                    handleMenuItem(menu, comp, sortarray, lastSeparator);
143                    sortarray = new ArrayList<>();
144                    lastSeparator = 0;
145                }
146            } else if (item instanceof JSeparator) {
147                handleMenuItem(menu, comp, sortarray, lastSeparator);
148                sortarray = new ArrayList<>();
149                lastSeparator = i;
150            }
151        }
152    }
153
154    private static void handleMenuItem(JMenu menu, PresetTextComparator comp, List<JMenuItem> sortarray, int lastSeparator) {
155        sortarray.sort(comp);
156        int pos = 0;
157        for (JMenuItem menuItem : sortarray) {
158            int oldPos;
159            if (lastSeparator == 0) {
160                oldPos = pos;
161            } else {
162                oldPos = pos+lastSeparator+1;
163            }
164            menu.add(menuItem, oldPos);
165            pos++;
166        }
167    }
168}