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