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