// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.history; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trn; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.text.DateFormat; import java.util.Collections; import java.util.Date; import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextArea; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.osm.Changeset; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.User; import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; import org.openstreetmap.josm.gui.JosmUserIdentityManager; import org.openstreetmap.josm.gui.dialogs.ChangesetDialog; import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetCacheManager; import org.openstreetmap.josm.gui.dialogs.changeset.ChangesetDiscussionPanel; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.gui.widgets.JMultilineLabel; import org.openstreetmap.josm.gui.widgets.UrlLabel; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Utils; import org.openstreetmap.josm.tools.date.DateUtils; /** * VersionInfoPanel is an UI component which displays the basic properties of a version * of a {@link OsmPrimitive}. * @since 1709 */ public class VersionInfoPanel extends JPanel implements ChangeListener { private final PointInTimeType pointInTimeType; private final transient HistoryBrowserModel model; private JMultilineLabel lblInfo; private UrlLabel lblUser; private UrlLabel lblChangeset; private final JButton lblChangesetComments = new JButton(ImageProvider.get("dialogs/notes/note_comment")); private final OpenChangesetDialogAction changesetCommentsDialogAction = new OpenChangesetDialogAction(ChangesetDiscussionPanel.class); private final OpenChangesetDialogAction changesetDialogAction = new OpenChangesetDialogAction(null); private final JButton changesetButton = new JButton(changesetDialogAction); private JPanel pnlChangesetSource; private JPanel pnlChangesetImageryUsed; private JLabel lblSource; private JLabel lblImageryUsed; private JTextArea texChangesetComment; private JTextArea texChangesetSource; private JTextArea texChangesetImageryUsed; protected static JTextArea buildTextArea(String tooltip) { JTextArea lbl = new JTextArea(); lbl.setLineWrap(true); lbl.setWrapStyleWord(true); lbl.setEditable(false); lbl.setOpaque(false); lbl.setToolTipText(tooltip); return lbl; } protected static JLabel buildLabel(String text, String tooltip, JTextArea textArea) { // We need text field to be a JTextArea for line wrapping but cannot put HTML code in here, // so create a separate JLabel with same characteristics (margin, font) JLabel lbl = new JLabel("<html><p style='margin-top:"+textArea.getMargin().top+"'>"+text+"</html>"); lbl.setFont(textArea.getFont()); lbl.setToolTipText(tooltip); lbl.setLabelFor(textArea); return lbl; } protected static JPanel buildTextPanel(JLabel label, JTextArea textArea) { JPanel pnl = new JPanel(new GridBagLayout()); pnl.add(label, GBC.std().anchor(GBC.NORTHWEST)); pnl.add(textArea, GBC.eol().insets(2, 0, 0, 0).fill()); return pnl; } protected void build() { JPanel pnl1 = new JPanel(new BorderLayout()); lblInfo = new JMultilineLabel(""); pnl1.add(lblInfo, BorderLayout.CENTER); JPanel pnlUserAndChangeset = new JPanel(new GridLayout(2, 2)); lblUser = new UrlLabel("", 2); pnlUserAndChangeset.add(new JLabel(tr("User:"))); pnlUserAndChangeset.add(lblUser); changesetButton.setMargin(new Insets(0, 0, 0, 0)); pnlUserAndChangeset.add(changesetButton); lblChangeset = new UrlLabel("", 2); final JPanel pnlChangesetInfo = new JPanel(new BorderLayout()); pnlChangesetInfo.add(lblChangeset, BorderLayout.CENTER); lblChangesetComments.setAction(changesetCommentsDialogAction); lblChangesetComments.setMargin(new Insets(0, 0, 0, 0)); lblChangesetComments.setIcon(new ImageProvider("dialogs/notes/note_comment").setMaxSize(12).get()); pnlChangesetInfo.add(lblChangesetComments, BorderLayout.EAST); pnlUserAndChangeset.add(pnlChangesetInfo); texChangesetComment = buildTextArea(tr("Changeset comment")); texChangesetSource = buildTextArea(tr("Changeset source")); texChangesetImageryUsed = buildTextArea(tr("Imagery used")); lblSource = buildLabel(tr("<b>Source</b>:"), tr("Changeset source"), texChangesetSource); lblImageryUsed = buildLabel(tr("<b>Imagery</b>:"), tr("Imagery used"), texChangesetImageryUsed); pnlChangesetSource = buildTextPanel(lblSource, texChangesetSource); pnlChangesetImageryUsed = buildTextPanel(lblImageryUsed, texChangesetImageryUsed); setLayout(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); gc.anchor = GridBagConstraints.NORTHWEST; gc.fill = GridBagConstraints.HORIZONTAL; gc.weightx = 1.0; gc.weighty = 1.0; add(pnl1, gc); gc.gridy = 1; gc.weighty = 0.0; add(pnlUserAndChangeset, gc); gc.gridy = 2; add(texChangesetComment, gc); gc.gridy = 3; add(pnlChangesetSource, gc); gc.gridy = 4; add(pnlChangesetImageryUsed, gc); } protected HistoryOsmPrimitive getPrimitive() { if (model == null || pointInTimeType == null) return null; return model.getPointInTime(pointInTimeType); } protected String getInfoText(final Date timestamp, final long version, final boolean isLatest) { String text; if (isLatest) { OsmDataLayer editLayer = Main.getLayerManager().getEditLayer(); text = tr("<html>Version <strong>{0}</strong> currently edited in layer ''{1}''</html>", Long.toString(version), editLayer == null ? tr("unknown") : Utils.escapeReservedCharactersHTML(editLayer.getName()) ); } else { String date = "?"; if (timestamp != null) { date = DateUtils.formatDateTime(timestamp, DateFormat.SHORT, DateFormat.SHORT); } text = tr( "<html>Version <strong>{0}</strong> created on <strong>{1}</strong></html>", Long.toString(version), date); } return text; } /** * Constructs a new {@code VersionInfoPanel}. */ public VersionInfoPanel() { pointInTimeType = null; model = null; build(); } /** * constructor * * @param model the model (must not be null) * @param pointInTimeType the point in time this panel visualizes (must not be null) * @throws IllegalArgumentException if model is null * @throws IllegalArgumentException if pointInTimeType is null */ public VersionInfoPanel(HistoryBrowserModel model, PointInTimeType pointInTimeType) { CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); CheckParameterUtil.ensureParameterNotNull(model, "model"); this.model = model; this.pointInTimeType = pointInTimeType; model.addChangeListener(this); build(); } protected static String getUserUrl(String username) { return Main.getBaseUserUrl() + '/' + Utils.encodeUrl(username).replaceAll("\\+", "%20"); } @Override public void stateChanged(ChangeEvent e) { HistoryOsmPrimitive primitive = getPrimitive(); if (primitive != null) { Changeset cs = primitive.getChangeset(); update(cs, model.isLatest(primitive), primitive.getTimestamp(), primitive.getVersion()); } } /** * Updates the content of this panel based on the changeset information given by {@code primitive}. * @param primitive the primitive to extract the changeset information from * @param isLatest whether this relates to a not yet commited changeset */ public void update(final OsmPrimitive primitive, final boolean isLatest) { update(Changeset.fromPrimitive(primitive), isLatest, primitive.getTimestamp(), primitive.getVersion()); } /** * Updates the content of this panel based on the changeset information given by {@code cs}. * @param cs the changeset information * @param isLatest whether this relates to a not yet commited changeset * @param timestamp the timestamp * @param version the version of the primitive */ public void update(final Changeset cs, final boolean isLatest, final Date timestamp, final long version) { lblInfo.setText(getInfoText(timestamp, version, isLatest)); if (!isLatest && cs != null) { User user = cs.getUser(); String url = Main.getBaseBrowseUrl() + "/changeset/" + cs.getId(); lblChangeset.setUrl(url); lblChangeset.setDescription(Long.toString(cs.getId())); changesetCommentsDialogAction.setId(cs.getId()); lblChangesetComments.setVisible(cs.getCommentsCount() > 0); lblChangesetComments.setText(String.valueOf(cs.getCommentsCount())); lblChangesetComments.setToolTipText(trn("This changeset has {0} comment", "This changeset has {0} comments", cs.getCommentsCount(), cs.getCommentsCount())); changesetDialogAction.setId(cs.getId()); changesetButton.setEnabled(true); String username = ""; if (user != null) { username = user.getName(); } lblUser.setDescription(username); if (user != null && user != User.getAnonymous()) { lblUser.setUrl(getUserUrl(username)); } else { lblUser.setUrl(null); } } else { String username = JosmUserIdentityManager.getInstance().getUserName(); if (username == null) { lblUser.setDescription(tr("anonymous")); lblUser.setUrl(null); } else { lblUser.setDescription(username); lblUser.setUrl(getUserUrl(username)); } lblChangeset.setDescription(tr("none")); lblChangeset.setUrl(null); lblChangesetComments.setVisible(false); changesetDialogAction.setId(null); changesetButton.setEnabled(false); } final Changeset oppCs = model != null ? model.getPointInTime(pointInTimeType.opposite()).getChangeset() : null; updateText(cs, "comment", texChangesetComment, null, oppCs, texChangesetComment); updateText(cs, "source", texChangesetSource, lblSource, oppCs, pnlChangesetSource); updateText(cs, "imagery_used", texChangesetImageryUsed, lblImageryUsed, oppCs, pnlChangesetImageryUsed); } protected static void updateText(Changeset cs, String attr, JTextArea textArea, JLabel label, Changeset oppCs, JComponent container) { final String text = cs != null ? cs.get(attr) : null; // Update text, hide prefixing label if empty if (label != null) { label.setVisible(text != null && !Utils.isStripEmpty(text)); } textArea.setText(text); // Hide container if values of both versions are empty container.setVisible(text != null || (oppCs != null && oppCs.get(attr) != null)); } static class OpenChangesetDialogAction extends AbstractAction { private final Class<? extends JComponent> componentToSelect; private Integer id; OpenChangesetDialogAction(Class<? extends JComponent> componentToSelect) { super(tr("Changeset"), new ImageProvider("dialogs/changeset", "changesetmanager").resetMaxSize(new Dimension(16, 16)).get()); putValue(SHORT_DESCRIPTION, tr("Opens the Changeset Manager window for the selected changesets")); this.componentToSelect = componentToSelect; } void setId(Integer id) { this.id = id; } @Override public void actionPerformed(ActionEvent e) { if (id != null) { ChangesetDialog.LaunchChangesetManager.displayChangesets(Collections.singleton(id)); } if (componentToSelect != null) { ChangesetCacheManager.getInstance().setSelectedComponentInDetailPanel(componentToSelect); } } } }