| | 1 | // License: GPL. For details, see LICENSE file. |
| | 2 | |
| | 3 | package org.openstreetmap.josm.gui; |
| | 4 | |
| | 5 | import javax.swing.JMenu; |
| | 6 | import javax.swing.JMenuItem; |
| | 7 | import java.awt.Component; |
| | 8 | import java.awt.event.ContainerEvent; |
| | 9 | import java.awt.event.ContainerListener; |
| | 10 | import java.beans.PropertyChangeEvent; |
| | 11 | import java.beans.PropertyChangeListener; |
| | 12 | import java.util.ArrayList; |
| | 13 | import java.util.Arrays; |
| | 14 | import java.util.List; |
| | 15 | import java.util.Objects; |
| | 16 | import java.util.stream.IntStream; |
| | 17 | import java.util.stream.Stream; |
| | 18 | |
| | 19 | /** |
| | 20 | * Represents a menu containing a popup menu. |
| | 21 | */ |
| | 22 | public class JosmMenu extends JMenu { |
| | 23 | |
| | 24 | /** |
| | 25 | * Constructs a new instance of {@link JosmMenu}. |
| | 26 | * @param label The label to be displayed on the menu. |
| | 27 | */ |
| | 28 | public JosmMenu(String label) { |
| | 29 | super(label); |
| | 30 | } |
| | 31 | |
| | 32 | /** |
| | 33 | * Gets a stream of {@link JMenuItem} objects contained within this menu. |
| | 34 | * @return A stream of {@link JMenuItem} contained within this menu. |
| | 35 | */ |
| | 36 | public Stream<JMenuItem> streamMenuItems() { |
| | 37 | return IntStream.range(0, this.getItemCount()) |
| | 38 | .mapToObj(this::getItem) |
| | 39 | .filter(Objects::nonNull) |
| | 40 | .filter(JMenuItem.class::isInstance); |
| | 41 | } |
| | 42 | |
| | 43 | /** |
| | 44 | * Adds a listener to the underlying {@link javax.swing.JPopupMenu} to react |
| | 45 | * on {@link JMenuItem} addition/removal. Mainly, adds {@link PropertyChangeListener} to every |
| | 46 | * item that disables the menu if none of containing items are enabled. |
| | 47 | */ |
| | 48 | public void addPopupContainerListener() { |
| | 49 | enabledPropertyChangeListener(null); // initial check |
| | 50 | |
| | 51 | super.getPopupMenu().addContainerListener(new ContainerListener() { |
| | 52 | |
| | 53 | /** |
| | 54 | * Keeps references to all listeners that were added, in order to be able to |
| | 55 | * delete them later if needed. |
| | 56 | */ |
| | 57 | private final List<PropertyChangeListener> listeners = new ArrayList<>(); |
| | 58 | |
| | 59 | @Override |
| | 60 | public void componentAdded(ContainerEvent e) { |
| | 61 | Component child = e.getChild(); |
| | 62 | if (child != null && child instanceof JMenuItem) { |
| | 63 | PropertyChangeListener l = ev -> enabledPropertyChangeListener(ev); |
| | 64 | listeners.add(l); |
| | 65 | child.addPropertyChangeListener("enabled", l); |
| | 66 | |
| | 67 | enabledPropertyChangeListener(null); |
| | 68 | } |
| | 69 | } |
| | 70 | |
| | 71 | @Override |
| | 72 | public void componentRemoved(ContainerEvent e) { |
| | 73 | Component child = e.getChild(); |
| | 74 | if (child != null && child instanceof JMenuItem) { |
| | 75 | PropertyChangeListener[] ls = child.getPropertyChangeListeners("enabled"); |
| | 76 | Arrays.stream(ls) |
| | 77 | .filter(this.listeners::contains) |
| | 78 | .findFirst() |
| | 79 | .ifPresent(l -> { |
| | 80 | this.listeners.remove(l); |
| | 81 | child.removePropertyChangeListener("enabled", l); |
| | 82 | |
| | 83 | enabledPropertyChangeListener(null); |
| | 84 | }); |
| | 85 | } |
| | 86 | } |
| | 87 | }); |
| | 88 | } |
| | 89 | |
| | 90 | private void enabledPropertyChangeListener(PropertyChangeEvent ev) { |
| | 91 | boolean enable = this.streamMenuItems().anyMatch(Component::isEnabled); |
| | 92 | this.setEnabled(enable); |
| | 93 | } |
| | 94 | } |