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