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