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