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    }