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