001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.history; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.BorderLayout; 007import java.awt.GridBagConstraints; 008import java.awt.GridBagLayout; 009import java.awt.GridLayout; 010import java.io.UnsupportedEncodingException; 011import java.net.URLEncoder; 012import java.text.DateFormat; 013import java.util.Observable; 014import java.util.Observer; 015 016import javax.swing.JComponent; 017import javax.swing.JLabel; 018import javax.swing.JPanel; 019import javax.swing.JTextArea; 020 021import org.openstreetmap.josm.Main; 022import org.openstreetmap.josm.data.osm.Changeset; 023import org.openstreetmap.josm.data.osm.OsmPrimitive; 024import org.openstreetmap.josm.data.osm.User; 025import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; 026import org.openstreetmap.josm.gui.JosmUserIdentityManager; 027import org.openstreetmap.josm.gui.layer.OsmDataLayer; 028import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 029import org.openstreetmap.josm.gui.widgets.UrlLabel; 030import org.openstreetmap.josm.tools.CheckParameterUtil; 031import org.openstreetmap.josm.tools.GBC; 032import org.openstreetmap.josm.tools.Utils; 033import org.openstreetmap.josm.tools.date.DateUtils; 034 035/** 036 * VersionInfoPanel is an UI component which displays the basic properties of a version 037 * of a {@link OsmPrimitive}. 038 * @since 1709 039 */ 040public class VersionInfoPanel extends JPanel implements Observer{ 041 private PointInTimeType pointInTimeType; 042 private HistoryBrowserModel model; 043 private JMultilineLabel lblInfo; 044 private UrlLabel lblUser; 045 private UrlLabel lblChangeset; 046 private JPanel pnlChangesetSource; 047 private JLabel lblSource; 048 private JTextArea lblChangesetComment; 049 private JTextArea lblChangesetSource; 050 051 protected static JTextArea buildTextArea(String tooltip) { 052 JTextArea lbl = new JTextArea(); 053 lbl.setLineWrap(true); 054 lbl.setWrapStyleWord(true); 055 lbl.setEditable(false); 056 lbl.setOpaque(false); 057 lbl.setToolTipText(tooltip); 058 return lbl; 059 } 060 061 protected static JLabel buildLabel(String text, String tooltip, JTextArea textArea) { 062 // We need text field to be a JTextArea for line wrapping but cannot put HTML code in here, 063 // so create a separate JLabel with same characteristics (margin, font) 064 JLabel lbl = new JLabel("<html><p style='margin-top:"+textArea.getMargin().top+"'>"+text+"</html>"); 065 lbl.setFont(textArea.getFont()); 066 lbl.setToolTipText(tooltip); 067 return lbl; 068 } 069 070 protected static JPanel buildTextPanel(JLabel label, JTextArea textArea) { 071 JPanel pnl = new JPanel(new GridBagLayout()); 072 pnl.add(label, GBC.std().anchor(GBC.NORTHWEST)); 073 pnl.add(textArea, GBC.eol().fill()); 074 return pnl; 075 } 076 077 protected void build() { 078 JPanel pnl1 = new JPanel(new BorderLayout()); 079 lblInfo = new JMultilineLabel(""); 080 pnl1.add(lblInfo, BorderLayout.CENTER); 081 082 JPanel pnlUserAndChangeset = new JPanel(new GridLayout(2,2)); 083 lblUser = new UrlLabel("", 2); 084 pnlUserAndChangeset.add(new JLabel(tr("User:"))); 085 pnlUserAndChangeset.add(lblUser); 086 pnlUserAndChangeset.add(new JLabel(tr("Changeset:"))); 087 lblChangeset = new UrlLabel("", 2); 088 pnlUserAndChangeset.add(lblChangeset); 089 090 lblChangesetComment = buildTextArea(tr("Changeset comment")); 091 lblChangesetSource = buildTextArea(tr("Changeset source")); 092 093 lblSource = buildLabel(tr("<b>Source</b>:"), tr("Changeset source"), lblChangesetSource); 094 pnlChangesetSource = buildTextPanel(lblSource, lblChangesetSource); 095 096 setLayout(new GridBagLayout()); 097 GridBagConstraints gc = new GridBagConstraints(); 098 gc.anchor = GridBagConstraints.NORTHWEST; 099 gc.fill = GridBagConstraints.HORIZONTAL; 100 gc.weightx = 1.0; 101 gc.weighty = 1.0; 102 add(pnl1, gc); 103 gc.gridy = 1; 104 gc.weighty = 0.0; 105 add(pnlUserAndChangeset, gc); 106 gc.gridy = 2; 107 add(lblChangesetComment, gc); 108 gc.gridy = 3; 109 add(pnlChangesetSource, gc); 110 } 111 112 protected HistoryOsmPrimitive getPrimitive() { 113 if (model == null || pointInTimeType == null) 114 return null; 115 return model.getPointInTime(pointInTimeType); 116 } 117 118 protected String getInfoText() { 119 HistoryOsmPrimitive primitive = getPrimitive(); 120 if (primitive == null) 121 return ""; 122 String text; 123 if (model.isLatest(primitive)) { 124 OsmDataLayer editLayer = Main.main.getEditLayer(); 125 text = tr("<html>Version <strong>{0}</strong> currently edited in layer ''{1}''</html>", 126 Long.toString(primitive.getVersion()), 127 editLayer == null ? tr("unknown") : editLayer.getName() 128 ); 129 } else { 130 String date = "?"; 131 if (primitive.getTimestamp() != null) { 132 date = DateUtils.formatDateTime(primitive.getTimestamp(), DateFormat.SHORT, DateFormat.SHORT); 133 } 134 text = tr( 135 "<html>Version <strong>{0}</strong> created on <strong>{1}</strong></html>", 136 Long.toString(primitive.getVersion()), date); 137 } 138 return text; 139 } 140 141 /** 142 * Constructs a new {@code VersionInfoPanel}. 143 */ 144 public VersionInfoPanel() { 145 pointInTimeType = null; 146 model = null; 147 build(); 148 } 149 150 /** 151 * constructor 152 * 153 * @param model the model (must not be null) 154 * @param pointInTimeType the point in time this panel visualizes (must not be null) 155 * @exception IllegalArgumentException thrown, if model is null 156 * @exception IllegalArgumentException thrown, if pointInTimeType is null 157 * 158 */ 159 public VersionInfoPanel(HistoryBrowserModel model, PointInTimeType pointInTimeType) throws IllegalArgumentException { 160 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 161 CheckParameterUtil.ensureParameterNotNull(model, "model"); 162 163 this.model = model; 164 this.pointInTimeType = pointInTimeType; 165 model.addObserver(this); 166 build(); 167 } 168 169 protected static String getUserUrl(String username) throws UnsupportedEncodingException { 170 return Main.getBaseUserUrl() + "/" + URLEncoder.encode(username, "UTF-8").replaceAll("\\+", "%20"); 171 } 172 173 @Override 174 public void update(Observable o, Object arg) { 175 lblInfo.setText(getInfoText()); 176 177 HistoryOsmPrimitive primitive = getPrimitive(); 178 Changeset cs = primitive.getChangeset(); 179 180 if (!model.isLatest(primitive)) { 181 User user = primitive.getUser(); 182 String url = Main.getBaseBrowseUrl() + "/changeset/" + primitive.getChangesetId(); 183 lblChangeset.setUrl(url); 184 lblChangeset.setDescription(Long.toString(primitive.getChangesetId())); 185 186 String username = ""; 187 if (user != null) { 188 username = user.getName(); 189 } 190 lblUser.setDescription(username); 191 try { 192 if (user != null && user != User.getAnonymous()) { 193 lblUser.setUrl(getUserUrl(username)); 194 } else { 195 lblUser.setUrl(null); 196 } 197 } catch(UnsupportedEncodingException e) { 198 Main.error(e); 199 lblUser.setUrl(null); 200 } 201 } else { 202 String username = JosmUserIdentityManager.getInstance().getUserName(); 203 if (username == null) { 204 lblUser.setDescription(tr("anonymous")); 205 lblUser.setUrl(null); 206 } else { 207 lblUser.setDescription(username); 208 try { 209 lblUser.setUrl(getUserUrl(username)); 210 } catch(UnsupportedEncodingException e) { 211 Main.error(e); 212 lblUser.setUrl(null); 213 } 214 } 215 lblChangeset.setDescription(tr("none")); 216 lblChangeset.setUrl(null); 217 } 218 219 final Changeset oppCs = model.getPointInTime(pointInTimeType.opposite()).getChangeset(); 220 updateText(cs, "comment", lblChangesetComment, null, oppCs, lblChangesetComment); 221 updateText(cs, "source", lblChangesetSource, lblSource, oppCs, pnlChangesetSource); 222 } 223 224 protected static void updateText(Changeset cs, String attr, JTextArea textArea, JLabel label, Changeset oppCs, JComponent container) { 225 final String text = cs != null ? cs.get(attr) : null; 226 // Update text, hide prefixing label if empty 227 if (label != null) { 228 label.setVisible(text != null && !Utils.strip(text).isEmpty()); 229 } 230 textArea.setText(text); 231 // Hide container if values of both versions are empty 232 container.setVisible(text != null || (oppCs != null && oppCs.get(attr) != null)); 233 } 234}