| 1 | // License: GPL. See LICENSE file for details. |
|---|
| 2 | package org.openstreetmap.josm.actions; |
|---|
| 3 | |
|---|
| 4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht; |
|---|
| 5 | import static org.openstreetmap.josm.tools.I18n.tr; |
|---|
| 6 | import static org.openstreetmap.josm.tools.I18n.trc; |
|---|
| 7 | import static org.openstreetmap.josm.tools.I18n.trn; |
|---|
| 8 | |
|---|
| 9 | import java.awt.Font; |
|---|
| 10 | import java.awt.GridBagLayout; |
|---|
| 11 | import java.awt.event.ActionEvent; |
|---|
| 12 | import java.awt.event.ItemEvent; |
|---|
| 13 | import java.awt.event.ItemListener; |
|---|
| 14 | import java.awt.event.KeyEvent; |
|---|
| 15 | import java.lang.reflect.InvocationTargetException; |
|---|
| 16 | import java.util.Collections; |
|---|
| 17 | import java.util.LinkedList; |
|---|
| 18 | import java.util.List; |
|---|
| 19 | import java.util.Set; |
|---|
| 20 | import java.util.TreeSet; |
|---|
| 21 | import javax.swing.BorderFactory; |
|---|
| 22 | import javax.swing.GroupLayout; |
|---|
| 23 | |
|---|
| 24 | import javax.swing.JCheckBox; |
|---|
| 25 | import javax.swing.JLabel; |
|---|
| 26 | import javax.swing.JOptionPane; |
|---|
| 27 | import javax.swing.JPanel; |
|---|
| 28 | import javax.swing.JScrollPane; |
|---|
| 29 | import javax.swing.JTextArea; |
|---|
| 30 | import javax.swing.JTextField; |
|---|
| 31 | import javax.swing.KeyStroke; |
|---|
| 32 | import javax.swing.SwingUtilities; |
|---|
| 33 | import javax.swing.border.EtchedBorder; |
|---|
| 34 | import javax.swing.plaf.basic.BasicComboBoxEditor; |
|---|
| 35 | |
|---|
| 36 | import org.openstreetmap.josm.Main; |
|---|
| 37 | import org.openstreetmap.josm.actions.downloadtasks.DownloadReferrersTask; |
|---|
| 38 | import org.openstreetmap.josm.data.osm.DataSet; |
|---|
| 39 | import org.openstreetmap.josm.data.osm.OsmPrimitive; |
|---|
| 40 | import org.openstreetmap.josm.data.osm.OsmPrimitiveType; |
|---|
| 41 | import org.openstreetmap.josm.data.osm.PrimitiveId; |
|---|
| 42 | import org.openstreetmap.josm.gui.ExtendedDialog; |
|---|
| 43 | import org.openstreetmap.josm.gui.io.DownloadPrimitivesTask; |
|---|
| 44 | import org.openstreetmap.josm.gui.layer.OsmDataLayer; |
|---|
| 45 | import org.openstreetmap.josm.gui.widgets.HistoryComboBox; |
|---|
| 46 | import org.openstreetmap.josm.gui.widgets.HtmlPanel; |
|---|
| 47 | import org.openstreetmap.josm.gui.widgets.OsmIdTextField; |
|---|
| 48 | import org.openstreetmap.josm.gui.widgets.OsmPrimitiveTypesComboBox; |
|---|
| 49 | import org.openstreetmap.josm.tools.GBC; |
|---|
| 50 | import org.openstreetmap.josm.tools.Shortcut; |
|---|
| 51 | import org.openstreetmap.josm.tools.Utils; |
|---|
| 52 | |
|---|
| 53 | /** |
|---|
| 54 | * Download an OsmPrimitive by specifying type and ID |
|---|
| 55 | * |
|---|
| 56 | * @author Matthias Julius |
|---|
| 57 | */ |
|---|
| 58 | public class DownloadPrimitiveAction extends JosmAction { |
|---|
| 59 | |
|---|
| 60 | public DownloadPrimitiveAction() { |
|---|
| 61 | super(tr("Download object..."), "downloadprimitive", tr("Download OSM object by ID."), |
|---|
| 62 | Shortcut.registerShortcut("system:download_primitive", tr("File: {0}", tr("Download object...")), KeyEvent.VK_O, Shortcut.CTRL_SHIFT), true); |
|---|
| 63 | putValue("help", ht("/Action/DownloadObject")); |
|---|
| 64 | } |
|---|
| 65 | |
|---|
| 66 | /** |
|---|
| 67 | * Restore the current history from the preferences |
|---|
| 68 | * |
|---|
| 69 | * @param cbHistory |
|---|
| 70 | */ |
|---|
| 71 | protected void restorePrimitivesHistory(HistoryComboBox cbHistory) { |
|---|
| 72 | List<String> cmtHistory = new LinkedList<String>(Main.pref.getCollection(getClass().getName() + ".primitivesHistory", new LinkedList<String>())); |
|---|
| 73 | // we have to reverse the history, because ComboBoxHistory will reverse it again |
|---|
| 74 | // in addElement() |
|---|
| 75 | // |
|---|
| 76 | Collections.reverse(cmtHistory); |
|---|
| 77 | cbHistory.setPossibleItems(cmtHistory); |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | /** |
|---|
| 81 | * Remind the current history in the preferences |
|---|
| 82 | * @param cbHistory |
|---|
| 83 | */ |
|---|
| 84 | protected void remindPrimitivesHistory(HistoryComboBox cbHistory) { |
|---|
| 85 | cbHistory.addCurrentItemToHistory(); |
|---|
| 86 | Main.pref.putCollection(getClass().getName() + ".primitivesHistory", cbHistory.getHistory()); |
|---|
| 87 | } |
|---|
| 88 | |
|---|
| 89 | public void actionPerformed(ActionEvent e) { |
|---|
| 90 | |
|---|
| 91 | JPanel all = new JPanel(); |
|---|
| 92 | GroupLayout layout = new GroupLayout(all); |
|---|
| 93 | all.setLayout(layout); |
|---|
| 94 | layout.setAutoCreateGaps(true); |
|---|
| 95 | layout.setAutoCreateContainerGaps(true); |
|---|
| 96 | |
|---|
| 97 | JLabel lbl1 = new JLabel(tr("Object type:")); |
|---|
| 98 | final OsmPrimitiveTypesComboBox cbType = new OsmPrimitiveTypesComboBox(); |
|---|
| 99 | 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 | dialog.showDialog(); |
|---|
| 184 | if (dialog.getValue() != 1) return; |
|---|
| 185 | Main.pref.putInteger("downloadprimitive.lasttype", cbType.getSelectedIndex()); |
|---|
| 186 | Main.pref.put("downloadprimitive.referrers", referrers.isSelected()); |
|---|
| 187 | Main.pref.put("downloadprimitive.full", full.isSelected()); |
|---|
| 188 | Main.pref.put("download.newlayer", layer.isSelected()); |
|---|
| 189 | |
|---|
| 190 | tfId.setType(cbType.getType()); |
|---|
| 191 | if(!tfId.readOsmIds()) { |
|---|
| 192 | JOptionPane.showMessageDialog( |
|---|
| 193 | Main.parent, |
|---|
| 194 | tr("Invalid ID list specified\n" |
|---|
| 195 | + "Cannot download object."), |
|---|
| 196 | tr("Information"), |
|---|
| 197 | JOptionPane.INFORMATION_MESSAGE |
|---|
| 198 | ); |
|---|
| 199 | return; |
|---|
| 200 | } |
|---|
| 201 | remindPrimitivesHistory(cbId); |
|---|
| 202 | processItems(layer.isSelected(), tfId.getIds(), referrers.isSelected(), full.isSelected()); |
|---|
| 203 | } |
|---|
| 204 | |
|---|
| 205 | /** |
|---|
| 206 | * @param newLayer if the data should be downloaded into a new layer |
|---|
| 207 | * @param ids |
|---|
| 208 | * @param downloadReferrers if the referrers of the object should be downloaded as well, i.e., parent relations, and for nodes, additionally, parent ways |
|---|
| 209 | * @param full if the members of a relation should be downloaded as well |
|---|
| 210 | */ |
|---|
| 211 | public static void processItems(boolean newLayer, final List<PrimitiveId> ids, boolean downloadReferrers, boolean full) { |
|---|
| 212 | OsmDataLayer layer = getEditLayer(); |
|---|
| 213 | if ((layer == null) || newLayer) { |
|---|
| 214 | layer = new OsmDataLayer(new DataSet(), OsmDataLayer.createNewName(), null); |
|---|
| 215 | Main.main.addLayer(layer); |
|---|
| 216 | } |
|---|
| 217 | final DownloadPrimitivesTask task = new DownloadPrimitivesTask(layer, ids, full); |
|---|
| 218 | Main.worker.submit(task); |
|---|
| 219 | |
|---|
| 220 | if (downloadReferrers) { |
|---|
| 221 | for (PrimitiveId id : ids) { |
|---|
| 222 | Main.worker.submit(new DownloadReferrersTask(layer, id)); |
|---|
| 223 | } |
|---|
| 224 | } |
|---|
| 225 | |
|---|
| 226 | Runnable showErrorsAndWarnings = new Runnable() { |
|---|
| 227 | @Override |
|---|
| 228 | public void run() { |
|---|
| 229 | Set<PrimitiveId> errs = task.getMissingPrimitives(); |
|---|
| 230 | if (errs != null && !errs.isEmpty()) { |
|---|
| 231 | final ExtendedDialog dlg = reportProblemDialog(errs, |
|---|
| 232 | trn("Object could not be downloaded", "Some objects could not be downloaded", errs.size()), |
|---|
| 233 | trn("One object could not be downloaded.<br>", |
|---|
| 234 | "{0} objects could not be downloaded.<br>", |
|---|
| 235 | errs.size(), |
|---|
| 236 | errs.size()) |
|---|
| 237 | + tr("The server replied with response code 404.<br>" |
|---|
| 238 | + "This usually means, the server does not know an object with the requested id."), |
|---|
| 239 | tr("missing objects:"), |
|---|
| 240 | JOptionPane.ERROR_MESSAGE |
|---|
| 241 | ); |
|---|
| 242 | try { |
|---|
| 243 | SwingUtilities.invokeAndWait(new Runnable() { |
|---|
| 244 | @Override |
|---|
| 245 | public void run() { |
|---|
| 246 | dlg.showDialog(); |
|---|
| 247 | } |
|---|
| 248 | }); |
|---|
| 249 | } catch (InterruptedException ex) { |
|---|
| 250 | } catch (InvocationTargetException ex) { |
|---|
| 251 | } |
|---|
| 252 | } |
|---|
| 253 | |
|---|
| 254 | Set<PrimitiveId> del = new TreeSet<PrimitiveId>(); |
|---|
| 255 | DataSet ds = getCurrentDataSet(); |
|---|
| 256 | for (PrimitiveId id : ids) { |
|---|
| 257 | OsmPrimitive osm = ds.getPrimitiveById(id); |
|---|
| 258 | if (osm != null && osm.isDeleted()) { |
|---|
| 259 | del.add(id); |
|---|
| 260 | } |
|---|
| 261 | } |
|---|
| 262 | if (!del.isEmpty()) { |
|---|
| 263 | final ExtendedDialog dlg = reportProblemDialog(del, |
|---|
| 264 | trn("Object deleted", "Objects deleted", del.size()), |
|---|
| 265 | trn( |
|---|
| 266 | "One downloaded object is deleted.", |
|---|
| 267 | "{0} downloaded objects are deleted.", |
|---|
| 268 | del.size(), |
|---|
| 269 | del.size()), |
|---|
| 270 | null, |
|---|
| 271 | JOptionPane.WARNING_MESSAGE |
|---|
| 272 | ); |
|---|
| 273 | SwingUtilities.invokeLater(new Runnable() { |
|---|
| 274 | @Override |
|---|
| 275 | public void run() { |
|---|
| 276 | dlg.showDialog(); |
|---|
| 277 | } |
|---|
| 278 | }); |
|---|
| 279 | } |
|---|
| 280 | } |
|---|
| 281 | }; |
|---|
| 282 | Main.worker.submit(showErrorsAndWarnings); |
|---|
| 283 | } |
|---|
| 284 | |
|---|
| 285 | private static ExtendedDialog reportProblemDialog(Set<PrimitiveId> errs, |
|---|
| 286 | String TITLE, String TEXT, String LIST_LABEL, int msgType) { |
|---|
| 287 | JPanel p = new JPanel(new GridBagLayout()); |
|---|
| 288 | p.add(new HtmlPanel(TEXT), GBC.eop()); |
|---|
| 289 | if (LIST_LABEL != null) { |
|---|
| 290 | JLabel missing = new JLabel(LIST_LABEL); |
|---|
| 291 | missing.setFont(missing.getFont().deriveFont(Font.PLAIN)); |
|---|
| 292 | p.add(missing, GBC.eol()); |
|---|
| 293 | } |
|---|
| 294 | JTextArea txt = new JTextArea(); |
|---|
| 295 | txt.setFont(new Font("Monospaced", txt.getFont().getStyle(), txt.getFont().getSize())); |
|---|
| 296 | txt.setEditable(false); |
|---|
| 297 | txt.setBackground(p.getBackground()); |
|---|
| 298 | txt.setColumns(40); |
|---|
| 299 | txt.setRows(1); |
|---|
| 300 | txt.setText(Utils.join(", ", errs)); |
|---|
| 301 | JScrollPane scroll = new JScrollPane(txt); |
|---|
| 302 | p.add(scroll, GBC.eop().weight(1.0, 0.0).fill(GBC.HORIZONTAL)); |
|---|
| 303 | |
|---|
| 304 | return new ExtendedDialog( |
|---|
| 305 | Main.parent, |
|---|
| 306 | TITLE, |
|---|
| 307 | new String[] { tr("Ok") }) |
|---|
| 308 | .setButtonIcons(new String[] { "ok" }) |
|---|
| 309 | .setIcon(msgType) |
|---|
| 310 | .setContent(p, false); |
|---|
| 311 | } |
|---|
| 312 | } |
|---|