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 public 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}