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}