001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.changeset.query; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Dimension; 007import java.awt.GridBagConstraints; 008import java.awt.GridBagLayout; 009import java.awt.Insets; 010import java.awt.event.FocusAdapter; 011import java.awt.event.FocusEvent; 012import java.net.MalformedURLException; 013import java.net.URL; 014import java.util.Arrays; 015import java.util.List; 016import java.util.stream.Collectors; 017 018import javax.swing.BorderFactory; 019import javax.swing.JLabel; 020import javax.swing.JPanel; 021import javax.swing.event.DocumentEvent; 022import javax.swing.event.DocumentListener; 023import javax.swing.event.HyperlinkEvent; 024 025import org.openstreetmap.josm.gui.widgets.HtmlPanel; 026import org.openstreetmap.josm.gui.widgets.JosmTextField; 027import org.openstreetmap.josm.io.ChangesetQuery; 028import org.openstreetmap.josm.io.ChangesetQuery.ChangesetQueryUrlException; 029import org.openstreetmap.josm.io.OsmApi; 030import org.openstreetmap.josm.spi.preferences.Config; 031import org.openstreetmap.josm.tools.ImageProvider; 032import org.openstreetmap.josm.tools.Logging; 033 034/** 035 * This panel allows to build a changeset query from an URL. 036 * @since 2689 037 */ 038public class UrlBasedQueryPanel extends JPanel { 039 040 private final JosmTextField tfUrl = new JosmTextField(); 041 private final JLabel lblValid = new JLabel(); 042 043 /** 044 * Constructs a new {@code UrlBasedQueryPanel}. 045 */ 046 public UrlBasedQueryPanel() { 047 build(); 048 } 049 050 protected JPanel buildURLPanel() { 051 JPanel pnl = new JPanel(new GridBagLayout()); 052 GridBagConstraints gc = new GridBagConstraints(); 053 gc.weightx = 0.0; 054 gc.fill = GridBagConstraints.HORIZONTAL; 055 gc.insets = new Insets(0, 0, 0, 5); 056 pnl.add(new JLabel(tr("URL: ")), gc); 057 058 gc.gridx = 1; 059 gc.weightx = 1.0; 060 gc.fill = GridBagConstraints.HORIZONTAL; 061 pnl.add(tfUrl, gc); 062 tfUrl.getDocument().addDocumentListener(new ChangetQueryUrlValidator()); 063 tfUrl.addFocusListener( 064 new FocusAdapter() { 065 @Override 066 public void focusGained(FocusEvent e) { 067 tfUrl.selectAll(); 068 } 069 } 070 ); 071 072 gc.gridx = 2; 073 gc.weightx = 0.0; 074 gc.fill = GridBagConstraints.HORIZONTAL; 075 pnl.add(lblValid, gc); 076 lblValid.setPreferredSize(new Dimension(20, 20)); 077 return pnl; 078 } 079 080 protected static List<String> getExamples() { 081 return Arrays.asList( 082 Config.getUrls().getOSMWebsite()+"/history?open=true", 083 OsmApi.getOsmApi().getBaseUrl()+"/changesets?open=true"); 084 } 085 086 protected JPanel buildHelpPanel() { 087 String apiUrl = OsmApi.getOsmApi().getBaseUrl(); 088 HtmlPanel pnl = new HtmlPanel(); 089 pnl.setText( 090 "<html><body>" 091 + tr("Please enter or paste an URL to retrieve changesets from the OSM API.") 092 + "<p><strong>" + tr("Examples") + "</strong></p>" 093 + "<ul>" 094 + String.join("", getExamples().stream().map( 095 s -> "<li><a href=\""+s+"\">"+s+"</a></li>").collect(Collectors.toList())) 096 + "</ul>" 097 + tr("Note that changeset queries are currently always submitted to ''{0}'', regardless of the " 098 + "host, port and path of the URL entered below.", apiUrl) 099 + "</body></html>" 100 ); 101 pnl.getEditorPane().addHyperlinkListener(e -> { 102 if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { 103 tfUrl.setText(e.getDescription()); 104 tfUrl.requestFocusInWindow(); 105 } 106 }); 107 return pnl; 108 } 109 110 protected final void build() { 111 setLayout(new GridBagLayout()); 112 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 113 114 GridBagConstraints gc = new GridBagConstraints(); 115 gc.weightx = 1.0; 116 gc.fill = GridBagConstraints.HORIZONTAL; 117 gc.insets = new Insets(0, 0, 10, 0); 118 add(buildHelpPanel(), gc); 119 120 gc.gridy = 1; 121 gc.weightx = 1.0; 122 gc.fill = GridBagConstraints.HORIZONTAL; 123 add(buildURLPanel(), gc); 124 125 gc.gridy = 2; 126 gc.weightx = 1.0; 127 gc.weighty = 1.0; 128 gc.fill = GridBagConstraints.BOTH; 129 add(new JPanel(), gc); 130 } 131 132 protected static boolean isValidChangesetQueryUrl(String text) { 133 return buildChangesetQuery(text) != null; 134 } 135 136 protected static ChangesetQuery buildChangesetQuery(String text) { 137 URL url = null; 138 try { 139 url = new URL(text); 140 } catch (MalformedURLException e) { 141 return null; 142 } 143 String path = url.getPath(); 144 if (path == null || (!path.endsWith("/changesets") && !path.endsWith("/history"))) 145 return null; 146 147 try { 148 return ChangesetQuery.buildFromUrlQuery(url.getQuery()); 149 } catch (ChangesetQueryUrlException e) { 150 Logging.warn(e); 151 return null; 152 } 153 } 154 155 /** 156 * Replies the {@link ChangesetQuery} specified in this panel. null, if no valid changeset query 157 * is specified. 158 * 159 * @return the changeset query 160 */ 161 public ChangesetQuery buildChangesetQuery() { 162 String value = tfUrl.getText().trim(); 163 return buildChangesetQuery(value); 164 } 165 166 /** 167 * Initializes HMI for user input. 168 */ 169 public void startUserInput() { 170 tfUrl.requestFocusInWindow(); 171 } 172 173 /** 174 * Validates text entered in the changeset query URL field on the fly 175 */ 176 class ChangetQueryUrlValidator implements DocumentListener { 177 protected String getCurrentFeedback() { 178 String fb = (String) lblValid.getClientProperty("valid"); 179 return fb == null ? "none" : fb; 180 } 181 182 protected void feedbackValid() { 183 if ("valid".equals(getCurrentFeedback())) 184 return; 185 lblValid.setIcon(ImageProvider.get("misc", "green_check")); 186 lblValid.setToolTipText(null); 187 lblValid.putClientProperty("valid", "valid"); 188 } 189 190 protected void feedbackInvalid() { 191 if ("invalid".equals(getCurrentFeedback())) 192 return; 193 lblValid.setIcon(ImageProvider.get("warning-small")); 194 lblValid.setToolTipText(tr("This changeset query URL is invalid")); 195 lblValid.putClientProperty("valid", "invalid"); 196 } 197 198 protected void feedbackNone() { 199 lblValid.setIcon(null); 200 lblValid.putClientProperty("valid", "none"); 201 } 202 203 protected void validate() { 204 String value = tfUrl.getText(); 205 if (value.trim().isEmpty()) { 206 feedbackNone(); 207 return; 208 } 209 value = value.trim(); 210 if (isValidChangesetQueryUrl(value)) { 211 feedbackValid(); 212 } else { 213 feedbackInvalid(); 214 } 215 } 216 217 @Override 218 public void changedUpdate(DocumentEvent e) { 219 validate(); 220 } 221 222 @Override 223 public void insertUpdate(DocumentEvent e) { 224 validate(); 225 } 226 227 @Override 228 public void removeUpdate(DocumentEvent e) { 229 validate(); 230 } 231 } 232}