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