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