001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions.relation;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.Rectangle;
008import java.awt.event.ActionEvent;
009import java.awt.event.ActionListener;
010import java.awt.event.KeyEvent;
011import java.util.Collections;
012import java.util.List;
013
014import javax.swing.AbstractAction;
015import javax.swing.JMenuItem;
016import javax.swing.JPopupMenu;
017import javax.swing.KeyStroke;
018import javax.swing.plaf.basic.BasicArrowButton;
019
020import org.openstreetmap.josm.actions.JosmAction;
021import org.openstreetmap.josm.data.UndoRedoHandler;
022import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener;
023import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
024import org.openstreetmap.josm.data.osm.Relation;
025import org.openstreetmap.josm.gui.MainApplication;
026import org.openstreetmap.josm.gui.SideButton;
027import org.openstreetmap.josm.gui.layer.Layer;
028import org.openstreetmap.josm.gui.layer.OsmDataLayer;
029import org.openstreetmap.josm.tools.ImageProvider;
030import org.openstreetmap.josm.tools.Shortcut;
031import org.openstreetmap.josm.tools.Utils;
032
033/**
034 * Action for accessing recent relations.
035 * @since 9668
036 */
037public class RecentRelationsAction extends JosmAction implements CommandQueueListener {
038
039    private final SideButton editButton;
040    private final BasicArrowButton arrow;
041    private final Shortcut shortcut;
042    private final LaunchEditorAction launchAction;
043
044    /**
045     * Constructs a new <code>RecentRelationsAction</code>.
046     * @param editButton edit button
047     */
048    public RecentRelationsAction(SideButton editButton) {
049        super(RecentRelationsAction.class.getName(), null, null, null, false, true);
050        this.editButton = editButton;
051        arrow = editButton.createArrow(this);
052        arrow.setToolTipText(tr("List of recent relations"));
053        UndoRedoHandler.getInstance().addCommandQueueListener(this);
054        enableArrow();
055        shortcut = Shortcut.registerShortcut("relationeditor:editrecentrelation",
056            tr("Relation Editor: {0}", tr("Open recent relation")), KeyEvent.VK_ESCAPE, Shortcut.SHIFT);
057        launchAction = new LaunchEditorAction();
058        MainApplication.registerActionShortcut(launchAction, shortcut);
059    }
060
061    /**
062     * Enables arrow button.
063     */
064    public void enableArrow() {
065        if (arrow != null) {
066            arrow.setVisible(getLastRelation() != null);
067        }
068    }
069
070    /**
071     * Returns the last relation.
072     * @return the last relation
073     */
074    public static Relation getLastRelation() {
075        List<Relation> recentRelations = getRecentRelationsOnActiveLayer();
076        if (Utils.isEmpty(recentRelations))
077            return null;
078        return recentRelations.stream().filter(RecentRelationsAction::isRelationListable)
079                .findFirst().orElse(null);
080    }
081
082    /**
083     * Determines if the given relation is listable in last relations.
084     * @param relation relation
085     * @return {@code true} if relation is non null, not deleted, and in current dataset
086     */
087    public static boolean isRelationListable(Relation relation) {
088        return relation != null &&
089            !relation.isDeleted() &&
090            MainApplication.getLayerManager().getEditDataSet().containsRelation(relation);
091    }
092
093    @Override
094    public void actionPerformed(ActionEvent e) {
095        RecentRelationsPopupMenu.launch(editButton, shortcut.getKeyStroke());
096    }
097
098    @Override
099    public void commandChanged(int queueSize, int redoSize) {
100        enableArrow();
101    }
102
103    @Override
104    protected void updateEnabledState() {
105        enableArrow();
106    }
107
108    @Override
109    public void destroy() {
110        MainApplication.unregisterActionShortcut(launchAction, shortcut);
111        UndoRedoHandler.getInstance().removeCommandQueueListener(this);
112        super.destroy();
113    }
114
115    /**
116     * Returns the list of recent relations on active layer.
117     * @return the list of recent relations on active layer
118     */
119    public static List<Relation> getRecentRelationsOnActiveLayer() {
120        if (!MainApplication.isDisplayingMapView())
121            return Collections.emptyList();
122        Layer activeLayer = MainApplication.getLayerManager().getActiveLayer();
123        if (!(activeLayer instanceof OsmDataLayer)) {
124            return Collections.emptyList();
125        } else {
126            return ((OsmDataLayer) activeLayer).getRecentRelations();
127        }
128    }
129
130    static class LaunchEditorAction extends AbstractAction {
131        @Override
132        public void actionPerformed(ActionEvent e) {
133            EditRelationAction.launchEditor(getLastRelation());
134        }
135    }
136
137    static class RecentRelationsPopupMenu extends JPopupMenu {
138        /**
139         * Constructs a new {@code RecentRelationsPopupMenu}.
140         * @param recentRelations list of recent relations
141         * @param keystroke key stroke for the first menu item
142         */
143        RecentRelationsPopupMenu(List<Relation> recentRelations, KeyStroke keystroke) {
144            boolean first = true;
145            for (Relation relation: recentRelations) {
146                if (!isRelationListable(relation))
147                    continue;
148                JMenuItem menuItem = new RecentRelationsMenuItem(relation);
149                if (first) {
150                    menuItem.setAccelerator(keystroke);
151                    first = false;
152                }
153                menuItem.setIcon(ImageProvider.getPadded(relation, ImageProvider.ImageSizes.MENU.getImageDimension()));
154                add(menuItem);
155            }
156        }
157
158        static void launch(Component parent, KeyStroke keystroke) {
159            if (parent.isShowing()) {
160                Rectangle r = parent.getBounds();
161                new RecentRelationsPopupMenu(getRecentRelationsOnActiveLayer(), keystroke).show(parent, r.x, r.y + r.height);
162            }
163        }
164    }
165
166    /**
167     * A specialized {@link JMenuItem} for presenting one entry of the relation history
168     */
169    static class RecentRelationsMenuItem extends JMenuItem implements ActionListener {
170        private final transient Relation relation;
171
172        RecentRelationsMenuItem(Relation relation) {
173            super(relation.getDisplayName(DefaultNameFormatter.getInstance()));
174            this.relation = relation;
175            addActionListener(this);
176        }
177
178        @Override
179        public void actionPerformed(ActionEvent e) {
180            EditRelationAction.launchEditor(relation);
181        }
182    }
183}