001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trc; 006 007import java.awt.Component; 008import java.awt.Dimension; 009import java.awt.event.KeyEvent; 010import java.awt.event.WindowEvent; 011import java.awt.event.WindowListener; 012import java.util.Arrays; 013import java.util.Collection; 014import java.util.Collections; 015import java.util.EnumSet; 016import java.util.LinkedList; 017import java.util.List; 018import java.util.stream.Collectors; 019 020import javax.swing.BorderFactory; 021import javax.swing.GroupLayout; 022import javax.swing.JLabel; 023import javax.swing.JOptionPane; 024import javax.swing.JPanel; 025import javax.swing.KeyStroke; 026import javax.swing.border.EtchedBorder; 027import javax.swing.plaf.basic.BasicComboBoxEditor; 028 029import org.openstreetmap.josm.Main; 030import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 031import org.openstreetmap.josm.data.osm.PrimitiveId; 032import org.openstreetmap.josm.data.osm.SimplePrimitiveId; 033import org.openstreetmap.josm.gui.ExtendedDialog; 034import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; 035import org.openstreetmap.josm.gui.widgets.HistoryComboBox; 036import org.openstreetmap.josm.gui.widgets.HtmlPanel; 037import org.openstreetmap.josm.gui.widgets.JosmTextField; 038import org.openstreetmap.josm.gui.widgets.OsmIdTextField; 039import org.openstreetmap.josm.gui.widgets.OsmPrimitiveTypesComboBox; 040import org.openstreetmap.josm.tools.Utils; 041 042/** 043 * Dialog prompt to user to let him choose OSM primitives by specifying their type and IDs. 044 * @since 6448, split from DownloadObjectDialog 045 */ 046public class OsmIdSelectionDialog extends ExtendedDialog implements WindowListener { 047 048 protected final JPanel panel = new JPanel(); 049 protected final OsmPrimitiveTypesComboBox cbType = new OsmPrimitiveTypesComboBox(); 050 protected final OsmIdTextField tfId = new OsmIdTextField(); 051 protected final HistoryComboBox cbId = new HistoryComboBox(); 052 protected final transient GroupLayout layout = new GroupLayout(panel); 053 054 public OsmIdSelectionDialog(Component parent, String title, String ... buttonTexts) { 055 super(parent, title, buttonTexts); 056 } 057 058 public OsmIdSelectionDialog(Component parent, String title, String[] buttonTexts, boolean modal) { 059 super(parent, title, buttonTexts, modal); 060 } 061 062 public OsmIdSelectionDialog(Component parent, String title, String[] buttonTexts, boolean modal, boolean disposeOnClose) { 063 super(parent, title, buttonTexts, modal, disposeOnClose); 064 } 065 066 protected void init() { 067 panel.setLayout(layout); 068 layout.setAutoCreateGaps(true); 069 layout.setAutoCreateContainerGaps(true); 070 071 JLabel lbl1 = new JLabel(tr("Object type:")); 072 lbl1.setLabelFor(cbType); 073 074 cbType.addItem(trc("osm object types", "mixed")); 075 cbType.setToolTipText(tr("Choose the OSM object type")); 076 JLabel lbl2 = new JLabel(tr("Object ID:")); 077 lbl2.setLabelFor(cbId); 078 079 cbId.setEditor(new BasicComboBoxEditor() { 080 @Override 081 protected JosmTextField createEditorComponent() { 082 return tfId; 083 } 084 }); 085 cbId.setToolTipText(tr("Enter the ID of the object that should be downloaded")); 086 restorePrimitivesHistory(cbId); 087 088 // forward the enter key stroke to the download button 089 tfId.getKeymap().removeKeyStrokeBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false)); 090 tfId.setPreferredSize(new Dimension(400, tfId.getPreferredSize().height)); 091 092 final String help1 = /* I18n: {0} and contains example strings not meant for translation. */ 093 tr("Object IDs can be separated by comma or space, for instance: {0}", 094 "<b>" + Utils.joinAsHtmlUnorderedList(Arrays.asList("1 2 5", "1,2,5")) + "</b>"); 095 final String help2 = /* I18n: {0} and contains example strings not meant for translation. {1}=n, {2}=w, {3}=r. */ 096 tr("In mixed mode, specify objects like this: {0}<br/>" 097 + "({1} stands for <i>node</i>, {2} for <i>way</i>, and {3} for <i>relation</i>)", 098 "<b>w123, n110, w12, r15</b>", "<b>n</b>", "<b>w</b>", "<b>r</b>"); 099 final String help3 = /* I18n: {0} and contains example strings not meant for translation. */ 100 tr("Ranges of object IDs are specified with a hyphen, for instance: {0}", 101 "<b>" + Utils.joinAsHtmlUnorderedList(Arrays.asList("w1-5", "n30-37", "r501-5")) + "</b>"); 102 HtmlPanel help = new HtmlPanel(help1 + "<br/>" + help2 + "<br/><br/>" + help3); 103 help.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)); 104 105 cbType.addItemListener(e -> { 106 tfId.setType(cbType.getType()); 107 tfId.performValidation(); 108 }); 109 110 final GroupLayout.SequentialGroup sequentialGroup = layout.createSequentialGroup() 111 .addGroup(layout.createParallelGroup() 112 .addComponent(lbl1) 113 .addComponent(cbType, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)) 114 .addGroup(layout.createParallelGroup() 115 .addComponent(lbl2) 116 .addComponent(cbId, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)); 117 118 final GroupLayout.ParallelGroup parallelGroup = layout.createParallelGroup() 119 .addGroup(layout.createSequentialGroup() 120 .addGroup(layout.createParallelGroup() 121 .addComponent(lbl1) 122 .addComponent(lbl2) 123 ) 124 .addGroup(layout.createParallelGroup() 125 .addComponent(cbType) 126 .addComponent(cbId)) 127 ); 128 129 for (Component i : getComponentsBeforeHelp()) { 130 sequentialGroup.addComponent(i); 131 parallelGroup.addComponent(i); 132 } 133 134 layout.setVerticalGroup(sequentialGroup.addComponent(help)); 135 layout.setHorizontalGroup(parallelGroup.addComponent(help)); 136 } 137 138 /** 139 * Let subclasses add custom components between the id input field and the help text 140 * @return the collections to add 141 */ 142 protected Collection<Component> getComponentsBeforeHelp() { 143 return Collections.emptySet(); 144 } 145 146 /** 147 * Allows subclasses to specify a different continue button index. If this button is pressed, the history is updated. 148 * @return the button index 149 */ 150 public int getContinueButtonIndex() { 151 return 1; 152 } 153 154 /** 155 * Restore the current history from the preferences 156 * 157 * @param cbHistory the {@link HistoryComboBox} to which the history is restored to 158 */ 159 protected void restorePrimitivesHistory(HistoryComboBox cbHistory) { 160 List<String> cmtHistory = new LinkedList<>( 161 Main.pref.getCollection(getClass().getName() + ".primitivesHistory", new LinkedList<String>())); 162 // we have to reverse the history, because ComboBoxHistory will reverse it again in addElement() 163 Collections.reverse(cmtHistory); 164 cbHistory.setPossibleItems(cmtHistory); 165 } 166 167 /** 168 * Remind the current history in the preferences 169 * 170 * @param cbHistory the {@link HistoryComboBox} of which to restore the history 171 */ 172 protected void remindPrimitivesHistory(HistoryComboBox cbHistory) { 173 cbHistory.addCurrentItemToHistory(); 174 Main.pref.putCollection(getClass().getName() + ".primitivesHistory", cbHistory.getHistory()); 175 } 176 177 /** 178 * Gets the requested OSM object IDs. 179 * 180 * @return The list of requested OSM object IDs 181 */ 182 public final List<PrimitiveId> getOsmIds() { 183 return tfId.getIds(); 184 } 185 186 @Override 187 public void setupDialog() { 188 setContent(panel, false); 189 cbType.setSelectedIndex(Main.pref.getInteger("downloadprimitive.lasttype", 0)); 190 tfId.setType(cbType.getType()); 191 if (Main.pref.getBoolean("downloadprimitive.autopaste", true)) { 192 tryToPasteFromClipboard(tfId, cbType); 193 } 194 setDefaultButton(getContinueButtonIndex()); 195 addWindowListener(this); 196 super.setupDialog(); 197 } 198 199 protected void tryToPasteFromClipboard(OsmIdTextField tfId, OsmPrimitiveTypesComboBox cbType) { 200 String buf = ClipboardUtils.getClipboardStringContent(); 201 if (buf == null || buf.isEmpty()) return; 202 if (buf.length() > Main.pref.getInteger("downloadprimitive.max-autopaste-length", 2000)) return; 203 final List<SimplePrimitiveId> ids = SimplePrimitiveId.fuzzyParse(buf); 204 if (!ids.isEmpty()) { 205 final String parsedText = ids.stream().map(x -> x.getType().getAPIName().charAt(0) + String.valueOf(x.getUniqueId())) 206 .collect(Collectors.joining(", ")); 207 tfId.tryToPasteFrom(parsedText); 208 final EnumSet<OsmPrimitiveType> types = ids.stream().map(SimplePrimitiveId::getType).collect( 209 Collectors.toCollection(() -> EnumSet.noneOf(OsmPrimitiveType.class))); 210 if (types.size() == 1) { 211 // select corresponding type 212 cbType.setSelectedItem(types.iterator().next()); 213 } else { 214 // select "mixed" 215 cbType.setSelectedIndex(3); 216 } 217 } else if (buf.matches("[\\d,v\\s]+")) { 218 //fallback solution for id1,id2,id3 format 219 tfId.tryToPasteFrom(buf); 220 } 221 } 222 223 @Override public void windowClosed(WindowEvent e) { 224 if (e != null && e.getComponent() == this && getValue() == getContinueButtonIndex()) { 225 Main.pref.putInteger("downloadprimitive.lasttype", cbType.getSelectedIndex()); 226 227 if (!tfId.readIds()) { 228 JOptionPane.showMessageDialog(getParent(), 229 tr("Invalid ID list specified\n" 230 + "Cannot continue."), 231 tr("Information"), 232 JOptionPane.INFORMATION_MESSAGE 233 ); 234 return; 235 } 236 237 remindPrimitivesHistory(cbId); 238 } 239 } 240 241 @Override public void windowOpened(WindowEvent e) { 242 // Do nothing 243 } 244 245 @Override public void windowClosing(WindowEvent e) { 246 // Do nothing 247 } 248 249 @Override public void windowIconified(WindowEvent e) { 250 // Do nothing 251 } 252 253 @Override public void windowDeiconified(WindowEvent e) { 254 // Do nothing 255 } 256 257 @Override public void windowActivated(WindowEvent e) { 258 // Do nothing 259 } 260 261 @Override public void windowDeactivated(WindowEvent e) { 262 // Do nothing 263 } 264}