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