// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.dialogs.properties; import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trn; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.util.Collection; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.UIManager; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.preferences.BooleanProperty; import org.openstreetmap.josm.data.preferences.CachingProperty; import org.openstreetmap.josm.data.preferences.ColorProperty; /** * Cell renderer of tags table. * @since 6314 */ public class PropertiesCellRenderer extends DefaultTableCellRenderer { private static final CachingProperty<Color> SELECTED_FG = new ColorProperty(marktr("Discardable key: selection Foreground"), Color.GRAY).cached(); private static final CachingProperty<Color> SELECTED_BG; private static final CachingProperty<Color> NORMAL_FG = new ColorProperty(marktr("Discardable key: foreground"), Color.GRAY).cached(); private static final CachingProperty<Color> NORMAL_BG; private static final CachingProperty<Boolean> DISCARDABLE = new BooleanProperty("display.discardable-keys", false).cached(); static { SELECTED_BG = new ColorProperty(marktr("Discardable key: selection Background"), Optional.ofNullable(UIManager.getColor("Table.selectionBackground")).orElse(Color.BLUE)).cached(); NORMAL_BG = new ColorProperty(marktr("Discardable key: background"), Optional.ofNullable(UIManager.getColor("Table.background")).orElse(Color.WHITE)).cached(); } private final Collection<TableCellRenderer> customRenderer = new CopyOnWriteArrayList<>(); private static void setColors(Component c, String key, boolean isSelected) { if (OsmPrimitive.getDiscardableKeys().contains(key)) { c.setForeground((isSelected ? SELECTED_FG : NORMAL_FG).get()); c.setBackground((isSelected ? SELECTED_BG : NORMAL_BG).get()); } else { c.setForeground(UIManager.getColor("Table."+(isSelected ? "selectionF" : "f")+"oreground")); c.setBackground(UIManager.getColor("Table."+(isSelected ? "selectionB" : "b")+"ackground")); } } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { for (TableCellRenderer renderer : customRenderer) { final Component component = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (component != null) { return component; } } if (value == null) return this; Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column); if (c instanceof JLabel) { String str = null; if (value instanceof String) { str = (String) value; } else if (value instanceof Map<?, ?>) { Map<?, ?> v = (Map<?, ?>) value; if (v.size() != 1) { // Multiple values: give user a short summary of the values Integer blankCount; Integer otherCount; if (v.get("") == null) { blankCount = 0; otherCount = v.size(); } else { blankCount = (Integer) v.get(""); otherCount = v.size()-1; } StringBuilder sb = new StringBuilder("<"); if (otherCount == 1) { // Find the non-blank value in the map v.entrySet().stream().filter(entry -> !Objects.equals(entry.getKey(), "")) /* I18n: properties display partial string joined with comma, first is count, second is value */ .findAny().ifPresent(entry -> sb.append(tr("{0} ''{1}''", entry.getValue().toString(), entry.getKey()))); } else { /* I18n: properties display partial string joined with comma */ sb.append(trn("{0} different", "{0} different", otherCount, otherCount)); } if (blankCount > 0) { /* I18n: properties display partial string joined with comma */ sb.append(trn(", {0} unset", ", {0} unset", blankCount, blankCount)); } sb.append('>'); str = sb.toString(); c.setFont(c.getFont().deriveFont(Font.ITALIC)); } else { // One value: display the value final Map.Entry<?, ?> entry = v.entrySet().iterator().next(); str = (String) entry.getKey(); } } ((JLabel) c).putClientProperty("html.disable", Boolean.TRUE); // Fix #8730 ((JLabel) c).setText(str); if (DISCARDABLE.get()) { String key = null; if (column == 0) { key = str; } else if (column == 1) { Object value0 = table.getModel().getValueAt(row, 0); if (value0 instanceof String) { key = (String) value0; } } setColors(c, key, isSelected); } } return c; } /** * Adds a custom table cell renderer to render cells of the tags table. * * If the renderer is not capable performing a {@link TableCellRenderer#getTableCellRendererComponent}, * it should return {@code null} to fall back to the * {@link PropertiesCellRenderer#getTableCellRendererComponent default implementation}. * @param renderer the renderer to add * @since 9149 */ public void addCustomRenderer(TableCellRenderer renderer) { customRenderer.add(renderer); } /** * Removes a custom table cell renderer. * @param renderer the renderer to remove * @since 9149 */ public void removeCustomRenderer(TableCellRenderer renderer) { customRenderer.remove(renderer); } }