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