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