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