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 final JosmTextField tfUrl = new JosmTextField(); 034 private final JLabel lblValid = new JLabel(); 035 036 /** 037 * Constructs a new {@code UrlBasedQueryPanel}. 038 */ 039 public UrlBasedQueryPanel() { 040 build(); 041 } 042 043 protected JPanel buildURLPanel() { 044 JPanel pnl = new JPanel(new GridBagLayout()); 045 GridBagConstraints gc = new GridBagConstraints(); 046 gc.weightx = 0.0; 047 gc.fill = GridBagConstraints.HORIZONTAL; 048 gc.insets = new Insets(0, 0, 0, 5); 049 pnl.add(new JLabel(tr("URL: ")), gc); 050 051 gc.gridx = 1; 052 gc.weightx = 1.0; 053 gc.fill = GridBagConstraints.HORIZONTAL; 054 pnl.add(tfUrl, gc); 055 tfUrl.getDocument().addDocumentListener(new ChangetQueryUrlValidator()); 056 tfUrl.addFocusListener( 057 new FocusAdapter() { 058 @Override 059 public void focusGained(FocusEvent e) { 060 tfUrl.selectAll(); 061 } 062 } 063 ); 064 065 gc.gridx = 2; 066 gc.weightx = 0.0; 067 gc.fill = GridBagConstraints.HORIZONTAL; 068 pnl.add(lblValid, gc); 069 lblValid.setPreferredSize(new Dimension(20, 20)); 070 return pnl; 071 } 072 073 protected JPanel buildHelpPanel() { 074 String apiUrl = OsmApi.getOsmApi().getBaseUrl(); 075 HtmlPanel pnl = new HtmlPanel(); 076 pnl.setText( 077 "<html><body>" 078 + tr("Please enter or paste an URL to retrieve changesets from the OSM API.") 079 + "<p><strong>" + tr("Examples") + "</strong></p>" 080 + "<ul>" 081 + "<li><a href=\""+Main.getOSMWebsite()+"/history?open=true\">"+Main.getOSMWebsite()+"/history?open=true</a></li>" 082 + "<li><a href=\""+apiUrl+"/changesets?open=true\">"+apiUrl+"/changesets?open=true</a></li>" 083 + "</ul>" 084 + tr("Note that changeset queries are currently always submitted to ''{0}'', regardless of the " 085 + "host, port and path of the URL entered below.", apiUrl) 086 + "</body></html>" 087 ); 088 pnl.getEditorPane().addHyperlinkListener( 089 new HyperlinkListener() { 090 @Override 091 public void hyperlinkUpdate(HyperlinkEvent e) { 092 if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { 093 tfUrl.setText(e.getDescription()); 094 tfUrl.requestFocusInWindow(); 095 } 096 } 097 } 098 ); 099 return pnl; 100 } 101 102 protected final void build() { 103 setLayout(new GridBagLayout()); 104 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 105 106 GridBagConstraints gc = new GridBagConstraints(); 107 gc.weightx = 1.0; 108 gc.fill = GridBagConstraints.HORIZONTAL; 109 gc.insets = new Insets(0, 0, 10, 0); 110 add(buildHelpPanel(), gc); 111 112 gc.gridy = 1; 113 gc.weightx = 1.0; 114 gc.fill = GridBagConstraints.HORIZONTAL; 115 add(buildURLPanel(), gc); 116 117 gc.gridy = 2; 118 gc.weightx = 1.0; 119 gc.weighty = 1.0; 120 gc.fill = GridBagConstraints.BOTH; 121 add(new JPanel(), gc); 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 if (path == null || !path.endsWith("/changesets")) 137 return null; 138 139 try { 140 return ChangesetQuery.buildFromUrlQuery(url.getQuery()); 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())) 173 return; 174 lblValid.setIcon(ImageProvider.get("dialogs", "valid")); 175 lblValid.setToolTipText(null); 176 lblValid.putClientProperty("valid", "valid"); 177 } 178 179 protected void feedbackInvalid() { 180 if ("invalid".equals(getCurrentFeedback())) 181 return; 182 lblValid.setIcon(ImageProvider.get("warning-small")); 183 lblValid.setToolTipText(tr("This changeset query URL is invalid")); 184 lblValid.putClientProperty("valid", "invalid"); 185 } 186 187 protected void feedbackNone() { 188 lblValid.setIcon(null); 189 lblValid.putClientProperty("valid", "none"); 190 } 191 192 protected void validate() { 193 String value = tfUrl.getText(); 194 if (value.trim().isEmpty()) { 195 feedbackNone(); 196 return; 197 } 198 value = value.trim(); 199 if (isValidChangesetQueryUrl(value)) { 200 feedbackValid(); 201 } else { 202 feedbackInvalid(); 203 } 204 } 205 206 @Override 207 public void changedUpdate(DocumentEvent e) { 208 validate(); 209 } 210 211 @Override 212 public void insertUpdate(DocumentEvent e) { 213 validate(); 214 } 215 216 @Override 217 public void removeUpdate(DocumentEvent e) { 218 validate(); 219 } 220 } 221}