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}