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