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