001 // License: GPL. See LICENSE file for details. 002 package org.openstreetmap.josm.actions; 003 004 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005 import static org.openstreetmap.josm.tools.I18n.tr; 006 import static org.openstreetmap.josm.tools.I18n.trc; 007 import static org.openstreetmap.josm.tools.I18n.trn; 008 009 import java.awt.Font; 010 import java.awt.GridBagLayout; 011 import java.awt.event.ActionEvent; 012 import java.awt.event.ItemEvent; 013 import java.awt.event.ItemListener; 014 import java.awt.event.KeyEvent; 015 import java.lang.reflect.InvocationTargetException; 016 import java.util.Collections; 017 import java.util.LinkedList; 018 import java.util.List; 019 import java.util.Set; 020 import java.util.TreeSet; 021 import javax.swing.BorderFactory; 022 import javax.swing.GroupLayout; 023 024 import javax.swing.JCheckBox; 025 import javax.swing.JLabel; 026 import javax.swing.JOptionPane; 027 import javax.swing.JPanel; 028 import javax.swing.JScrollPane; 029 import javax.swing.JTextArea; 030 import javax.swing.JTextField; 031 import javax.swing.KeyStroke; 032 import javax.swing.SwingUtilities; 033 import javax.swing.border.EtchedBorder; 034 import javax.swing.plaf.basic.BasicComboBoxEditor; 035 036 import org.openstreetmap.josm.Main; 037 import org.openstreetmap.josm.actions.downloadtasks.DownloadReferrersTask; 038 import org.openstreetmap.josm.data.osm.DataSet; 039 import org.openstreetmap.josm.data.osm.OsmPrimitive; 040 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 041 import org.openstreetmap.josm.data.osm.PrimitiveId; 042 import org.openstreetmap.josm.gui.ExtendedDialog; 043 import org.openstreetmap.josm.gui.io.DownloadPrimitivesTask; 044 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 045 import org.openstreetmap.josm.gui.widgets.HistoryComboBox; 046 import org.openstreetmap.josm.gui.widgets.HtmlPanel; 047 import org.openstreetmap.josm.gui.widgets.OsmIdTextField; 048 import org.openstreetmap.josm.gui.widgets.OsmPrimitiveTypesComboBox; 049 import org.openstreetmap.josm.tools.GBC; 050 import org.openstreetmap.josm.tools.Shortcut; 051 import org.openstreetmap.josm.tools.Utils; 052 053 /** 054 * Download an OsmPrimitive by specifying type and ID 055 * 056 * @author Matthias Julius 057 */ 058 public class DownloadPrimitiveAction extends JosmAction { 059 060 public DownloadPrimitiveAction() { 061 super(tr("Download object..."), "downloadprimitive", tr("Download OSM object by ID."), 062 Shortcut.registerShortcut("system:download_primitive", tr("File: {0}", tr("Download object...")), KeyEvent.VK_O, Shortcut.CTRL_SHIFT), true); 063 putValue("help", ht("/Action/DownloadObject")); 064 } 065 066 /** 067 * Restore the current history from the preferences 068 * 069 * @param cbHistory 070 */ 071 protected void restorePrimitivesHistory(HistoryComboBox cbHistory) { 072 List<String> cmtHistory = new LinkedList<String>(Main.pref.getCollection(getClass().getName() + ".primitivesHistory", new LinkedList<String>())); 073 // we have to reverse the history, because ComboBoxHistory will reverse it again 074 // in addElement() 075 // 076 Collections.reverse(cmtHistory); 077 cbHistory.setPossibleItems(cmtHistory); 078 } 079 080 /** 081 * Remind the current history in the preferences 082 * @param cbHistory 083 */ 084 protected void remindPrimitivesHistory(HistoryComboBox cbHistory) { 085 cbHistory.addCurrentItemToHistory(); 086 Main.pref.putCollection(getClass().getName() + ".primitivesHistory", cbHistory.getHistory()); 087 } 088 089 public void actionPerformed(ActionEvent e) { 090 091 JPanel all = new JPanel(); 092 GroupLayout layout = new GroupLayout(all); 093 all.setLayout(layout); 094 layout.setAutoCreateGaps(true); 095 layout.setAutoCreateContainerGaps(true); 096 097 JLabel lbl1 = new JLabel(tr("Object type:")); 098 final OsmPrimitiveTypesComboBox cbType = new OsmPrimitiveTypesComboBox(); 099 cbType.addItem(trc("osm object types", "mixed")); 100 cbType.setToolTipText(tr("Choose the OSM object type")); 101 JLabel lbl2 = new JLabel(tr("Object ID:")); 102 final OsmIdTextField tfId = new OsmIdTextField(); 103 HistoryComboBox cbId = new HistoryComboBox(); 104 cbId.setEditor(new BasicComboBoxEditor() { 105 @Override 106 protected JTextField createEditorComponent() { 107 return tfId; 108 } 109 }); 110 cbId.setToolTipText(tr("Enter the ID of the object that should be downloaded")); 111 restorePrimitivesHistory(cbId); 112 // forward the enter key stroke to the download button 113 tfId.getKeymap().removeKeyStrokeBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false)); 114 JCheckBox layer = new JCheckBox(tr("Separate Layer")); 115 layer.setToolTipText(tr("Select if the data should be downloaded into a new layer")); 116 layer.setSelected(Main.pref.getBoolean("download.newlayer")); 117 final JCheckBox referrers = new JCheckBox(tr("Download referrers (parent relations)")); 118 referrers.setToolTipText(tr("Select if the referrers of the object should be downloaded as well, i.e.," 119 + "parent relations and for nodes, additionally, parent ways")); 120 referrers.setSelected(Main.pref.getBoolean("downloadprimitive.referrers", true)); 121 JCheckBox full = new JCheckBox(tr("Download relation members")); 122 full.setToolTipText(tr("Select if the members of a relation should be downloaded as well")); 123 full.setSelected(Main.pref.getBoolean("downloadprimitive.full", true)); 124 HtmlPanel help = new HtmlPanel(tr("Object IDs can be separated by comma or space.<br/>" 125 + " Examples: <b><ul><li>1 2 5</li><li>1,2,5</li></ul><br/></b>" 126 + " In mixed mode, specify objects like this: <b>w123, n110, w12, r15</b><br/>")); 127 help.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)); 128 129 layout.setVerticalGroup(layout.createSequentialGroup() 130 .addGroup(layout.createParallelGroup() 131 .addComponent(lbl1) 132 .addComponent(cbType, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)) 133 .addGroup(layout.createParallelGroup() 134 .addComponent(lbl2) 135 .addComponent(cbId, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)) 136 .addComponent(referrers) 137 .addComponent(full) 138 .addComponent(layer) 139 .addComponent(help) 140 ); 141 142 cbType.addItemListener(new ItemListener() { 143 144 @Override 145 public void itemStateChanged(ItemEvent e) { 146 tfId.setType(cbType.getType()); 147 tfId.performValidation(); 148 referrers.setText(cbType.getType() == OsmPrimitiveType.NODE 149 ? tr("Download referrers (parent relations and ways)") 150 : tr("Download referrers (parent relations)")); 151 } 152 }); 153 154 layout.setHorizontalGroup(layout.createParallelGroup() 155 .addGroup(layout.createSequentialGroup() 156 .addGroup(layout.createParallelGroup() 157 .addComponent(lbl1) 158 .addComponent(lbl2) 159 ) 160 .addGroup(layout.createParallelGroup() 161 .addComponent(cbType) 162 .addComponent(cbId)) 163 ) 164 .addComponent(referrers) 165 .addComponent(full) 166 .addComponent(layer) 167 .addComponent(help) 168 ); 169 170 ExtendedDialog dialog = new ExtendedDialog(Main.parent, 171 tr("Download object"), 172 new String[] {tr("Download object"), tr("Cancel")} 173 ); 174 dialog.setContent(all, false); 175 dialog.setButtonIcons(new String[] {"download.png", "cancel.png"}); 176 dialog.setToolTipTexts(new String[] { 177 tr("Start downloading"), 178 tr("Close dialog and cancel downloading") 179 }); 180 dialog.setDefaultButton(1); 181 dialog.configureContextsensitiveHelp("/Action/DownloadObject", true /* show help button */); 182 cbType.setSelectedIndex(Main.pref.getInteger("downloadprimitive.lasttype", 0)); 183 tfId.setType(cbType.getType()); 184 if (Main.pref.getBoolean("downloadprimitive.autopaste", true)) { 185 tryToPasteFromClipboard(tfId, cbType); 186 } 187 188 dialog.showDialog(); 189 if (dialog.getValue() != 1) return; 190 Main.pref.putInteger("downloadprimitive.lasttype", cbType.getSelectedIndex()); 191 Main.pref.put("downloadprimitive.referrers", referrers.isSelected()); 192 Main.pref.put("downloadprimitive.full", full.isSelected()); 193 Main.pref.put("download.newlayer", layer.isSelected()); 194 195 tfId.setType(cbType.getType()); 196 if(!tfId.readOsmIds()) { 197 JOptionPane.showMessageDialog( 198 Main.parent, 199 tr("Invalid ID list specified\n" 200 + "Cannot download object."), 201 tr("Information"), 202 JOptionPane.INFORMATION_MESSAGE 203 ); 204 return; 205 } 206 remindPrimitivesHistory(cbId); 207 processItems(layer.isSelected(), tfId.getIds(), referrers.isSelected(), full.isSelected()); 208 } 209 210 /** 211 * @param newLayer if the data should be downloaded into a new layer 212 * @param ids 213 * @param downloadReferrers if the referrers of the object should be downloaded as well, i.e., parent relations, and for nodes, additionally, parent ways 214 * @param full if the members of a relation should be downloaded as well 215 */ 216 public static void processItems(boolean newLayer, final List<PrimitiveId> ids, boolean downloadReferrers, boolean full) { 217 OsmDataLayer layer = getEditLayer(); 218 if ((layer == null) || newLayer) { 219 layer = new OsmDataLayer(new DataSet(), OsmDataLayer.createNewName(), null); 220 Main.main.addLayer(layer); 221 } 222 final DownloadPrimitivesTask task = new DownloadPrimitivesTask(layer, ids, full); 223 Main.worker.submit(task); 224 225 if (downloadReferrers) { 226 for (PrimitiveId id : ids) { 227 Main.worker.submit(new DownloadReferrersTask(layer, id)); 228 } 229 } 230 231 Runnable showErrorsAndWarnings = new Runnable() { 232 @Override 233 public void run() { 234 Set<PrimitiveId> errs = task.getMissingPrimitives(); 235 if (errs != null && !errs.isEmpty()) { 236 final ExtendedDialog dlg = reportProblemDialog(errs, 237 trn("Object could not be downloaded", "Some objects could not be downloaded", errs.size()), 238 trn("One object could not be downloaded.<br>", 239 "{0} objects could not be downloaded.<br>", 240 errs.size(), 241 errs.size()) 242 + tr("The server replied with response code 404.<br>" 243 + "This usually means, the server does not know an object with the requested id."), 244 tr("missing objects:"), 245 JOptionPane.ERROR_MESSAGE 246 ); 247 try { 248 SwingUtilities.invokeAndWait(new Runnable() { 249 @Override 250 public void run() { 251 dlg.showDialog(); 252 } 253 }); 254 } catch (InterruptedException ex) { 255 } catch (InvocationTargetException ex) { 256 } 257 } 258 259 Set<PrimitiveId> del = new TreeSet<PrimitiveId>(); 260 DataSet ds = getCurrentDataSet(); 261 for (PrimitiveId id : ids) { 262 OsmPrimitive osm = ds.getPrimitiveById(id); 263 if (osm != null && osm.isDeleted()) { 264 del.add(id); 265 } 266 } 267 if (!del.isEmpty()) { 268 final ExtendedDialog dlg = reportProblemDialog(del, 269 trn("Object deleted", "Objects deleted", del.size()), 270 trn( 271 "One downloaded object is deleted.", 272 "{0} downloaded objects are deleted.", 273 del.size(), 274 del.size()), 275 null, 276 JOptionPane.WARNING_MESSAGE 277 ); 278 SwingUtilities.invokeLater(new Runnable() { 279 @Override 280 public void run() { 281 dlg.showDialog(); 282 } 283 }); 284 } 285 } 286 }; 287 Main.worker.submit(showErrorsAndWarnings); 288 } 289 290 private static ExtendedDialog reportProblemDialog(Set<PrimitiveId> errs, 291 String TITLE, String TEXT, String LIST_LABEL, int msgType) { 292 JPanel p = new JPanel(new GridBagLayout()); 293 p.add(new HtmlPanel(TEXT), GBC.eop()); 294 if (LIST_LABEL != null) { 295 JLabel missing = new JLabel(LIST_LABEL); 296 missing.setFont(missing.getFont().deriveFont(Font.PLAIN)); 297 p.add(missing, GBC.eol()); 298 } 299 JTextArea txt = new JTextArea(); 300 txt.setFont(new Font("Monospaced", txt.getFont().getStyle(), txt.getFont().getSize())); 301 txt.setEditable(false); 302 txt.setBackground(p.getBackground()); 303 txt.setColumns(40); 304 txt.setRows(1); 305 txt.setText(Utils.join(", ", errs)); 306 JScrollPane scroll = new JScrollPane(txt); 307 p.add(scroll, GBC.eop().weight(1.0, 0.0).fill(GBC.HORIZONTAL)); 308 309 return new ExtendedDialog( 310 Main.parent, 311 TITLE, 312 new String[] { tr("Ok") }) 313 .setButtonIcons(new String[] { "ok" }) 314 .setIcon(msgType) 315 .setContent(p, false); 316 } 317 318 private void tryToPasteFromClipboard(OsmIdTextField tfId, OsmPrimitiveTypesComboBox cbType) { 319 String buf = Utils.getClipboardContent(); 320 if (buf != null) { 321 if (buf.contains("node")) cbType.setSelectedIndex(0); 322 if (buf.contains("way")) cbType.setSelectedIndex(1); 323 if (buf.contains("relation")) cbType.setSelectedIndex(2); 324 String[] res = buf.split("/"); 325 String txt; 326 if (res.length>0) { 327 txt = res[res.length-1]; 328 if (txt.isEmpty() && txt.length()>1) txt=res[res.length-2]; 329 } else { 330 txt=buf; 331 } 332 tfId.setText(txt); 333 tfId.clearTextIfInvalid(); 334 } 335 } 336 }