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.actions.JosmAction; 021import org.openstreetmap.josm.data.UndoRedoHandler; 022import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener; 023import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 024import org.openstreetmap.josm.data.osm.Relation; 025import org.openstreetmap.josm.gui.MainApplication; 026import org.openstreetmap.josm.gui.SideButton; 027import org.openstreetmap.josm.gui.layer.Layer; 028import org.openstreetmap.josm.gui.layer.OsmDataLayer; 029import org.openstreetmap.josm.tools.ImageProvider; 030import org.openstreetmap.josm.tools.Shortcut; 031 032/** 033 * Action for accessing recent relations. 034 * @since 9668 035 */ 036public class RecentRelationsAction extends JosmAction implements CommandQueueListener { 037 038 private final SideButton editButton; 039 private final BasicArrowButton arrow; 040 private final Shortcut shortcut; 041 private final LaunchEditorAction launchAction; 042 043 /** 044 * Constructs a new <code>RecentRelationsAction</code>. 045 * @param editButton edit button 046 */ 047 public RecentRelationsAction(SideButton editButton) { 048 super(RecentRelationsAction.class.getName(), null, null, null, false, true); 049 this.editButton = editButton; 050 arrow = editButton.createArrow(this); 051 arrow.setToolTipText(tr("List of recent relations")); 052 UndoRedoHandler.getInstance().addCommandQueueListener(this); 053 enableArrow(); 054 shortcut = Shortcut.registerShortcut("relationeditor:editrecentrelation", 055 tr("Relation Editor: {0}", tr("Open recent relation")), KeyEvent.VK_ESCAPE, Shortcut.SHIFT); 056 launchAction = new LaunchEditorAction(); 057 MainApplication.registerActionShortcut(launchAction, shortcut); 058 } 059 060 /** 061 * Enables arrow button. 062 */ 063 public void enableArrow() { 064 if (arrow != null) { 065 arrow.setVisible(getLastRelation() != null); 066 } 067 } 068 069 /** 070 * Returns the last relation. 071 * @return the last relation 072 */ 073 public static Relation getLastRelation() { 074 List<Relation> recentRelations = getRecentRelationsOnActiveLayer(); 075 if (recentRelations == null || recentRelations.isEmpty()) 076 return null; 077 for (Relation relation: recentRelations) { 078 if (!isRelationListable(relation)) 079 continue; 080 return relation; 081 } 082 return null; 083 } 084 085 /** 086 * Determines if the given relation is listable in last relations. 087 * @param relation relation 088 * @return {@code true} if relation is non null, not deleted, and in current dataset 089 */ 090 public static boolean isRelationListable(Relation relation) { 091 return relation != null && 092 !relation.isDeleted() && 093 MainApplication.getLayerManager().getEditDataSet().containsRelation(relation); 094 } 095 096 @Override 097 public void actionPerformed(ActionEvent e) { 098 RecentRelationsPopupMenu.launch(editButton, shortcut.getKeyStroke()); 099 } 100 101 @Override 102 public void commandChanged(int queueSize, int redoSize) { 103 enableArrow(); 104 } 105 106 @Override 107 protected void updateEnabledState() { 108 enableArrow(); 109 } 110 111 @Override 112 public void destroy() { 113 MainApplication.unregisterActionShortcut(launchAction, shortcut); 114 UndoRedoHandler.getInstance().removeCommandQueueListener(this); 115 super.destroy(); 116 } 117 118 /** 119 * Returns the list of recent relations on active layer. 120 * @return the list of recent relations on active layer 121 */ 122 public static List<Relation> getRecentRelationsOnActiveLayer() { 123 if (!MainApplication.isDisplayingMapView()) 124 return Collections.emptyList(); 125 Layer activeLayer = MainApplication.getLayerManager().getActiveLayer(); 126 if (!(activeLayer instanceof OsmDataLayer)) { 127 return Collections.emptyList(); 128 } else { 129 return ((OsmDataLayer) activeLayer).getRecentRelations(); 130 } 131 } 132 133 static class LaunchEditorAction extends AbstractAction { 134 @Override 135 public void actionPerformed(ActionEvent e) { 136 EditRelationAction.launchEditor(getLastRelation()); 137 } 138 } 139 140 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 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 static void launch(Component parent, KeyStroke keystroke) { 162 if (parent.isShowing()) { 163 Rectangle r = parent.getBounds(); 164 new RecentRelationsPopupMenu(getRecentRelationsOnActiveLayer(), keystroke).show(parent, r.x, r.y + r.height); 165 } 166 } 167 } 168 169 /** 170 * A specialized {@link JMenuItem} for presenting one entry of the relation history 171 */ 172 static class RecentRelationsMenuItem extends JMenuItem implements ActionListener { 173 private final transient Relation relation; 174 175 RecentRelationsMenuItem(Relation relation) { 176 super(relation.getDisplayName(DefaultNameFormatter.getInstance())); 177 this.relation = relation; 178 addActionListener(this); 179 } 180 181 @Override 182 public void actionPerformed(ActionEvent e) { 183 EditRelationAction.launchEditor(relation); 184 } 185 } 186}