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