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