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 LaunchEditorAction(), 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.getLayerManager().getEditDataSet().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 layerAdded(LayerAddEvent e) {
112        enableArrow();
113    }
114
115    @Override
116    public void layerRemoving(LayerRemoveEvent e) {
117        enableArrow();
118    }
119
120    @Override
121    public void layerOrderChanged(LayerOrderChangeEvent e) {
122        enableArrow();
123    }
124
125    @Override
126    public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
127        enableArrow();
128    }
129
130    /**
131     * Returns the list of recent relations on active layer.
132     * @return the list of recent relations on active layer
133     */
134    public static List<Relation> getRecentRelationsOnActiveLayer() {
135        if (!Main.isDisplayingMapView())
136            return Collections.emptyList();
137        Layer activeLayer = Main.getLayerManager().getActiveLayer();
138        if (!(activeLayer instanceof OsmDataLayer)) {
139            return Collections.emptyList();
140        } else {
141            return ((OsmDataLayer) activeLayer).getRecentRelations();
142        }
143    }
144
145    protected static class LaunchEditorAction extends AbstractAction {
146        @Override
147        public void actionPerformed(ActionEvent e) {
148            EditRelationAction.launchEditor(getLastRelation());
149        }
150    }
151
152    protected static class RecentRelationsPopupMenu extends JPopupMenu {
153        /**
154         * Constructs a new {@code RecentRelationsPopupMenu}.
155         * @param recentRelations list of recent relations
156         * @param keystroke key stroke for the first menu item
157         */
158        public RecentRelationsPopupMenu(List<Relation> recentRelations, KeyStroke keystroke) {
159            boolean first = true;
160            for (Relation relation: recentRelations) {
161                if (!isRelationListable(relation))
162                    continue;
163                JMenuItem menuItem = new RecentRelationsMenuItem(relation);
164                if (first) {
165                    menuItem.setAccelerator(keystroke);
166                    first = false;
167                }
168                menuItem.setIcon(ImageProvider.getPadded(relation, ImageProvider.ImageSizes.MENU.getImageDimension()));
169                add(menuItem);
170            }
171        }
172
173        protected static void launch(Component parent, KeyStroke keystroke) {
174            Rectangle r = parent.getBounds();
175            new RecentRelationsPopupMenu(getRecentRelationsOnActiveLayer(), keystroke).show(parent, r.x, r.y + r.height);
176        }
177    }
178
179    /**
180     * A specialized {@link JMenuItem} for presenting one entry of the relation history
181     */
182    protected static class RecentRelationsMenuItem extends JMenuItem implements ActionListener {
183        protected final transient Relation relation;
184
185        public RecentRelationsMenuItem(Relation relation) {
186            super(relation.getDisplayName(DefaultNameFormatter.getInstance()));
187            this.relation = relation;
188            addActionListener(this);
189        }
190
191        @Override
192        public void actionPerformed(ActionEvent e) {
193            EditRelationAction.launchEditor(relation);
194        }
195    }
196
197}