001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.properties; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006import static org.openstreetmap.josm.tools.I18n.trn; 007 008import java.awt.Color; 009import java.awt.Component; 010import java.awt.Font; 011import java.util.Collection; 012import java.util.Map; 013import java.util.Objects; 014import java.util.Optional; 015import java.util.concurrent.CopyOnWriteArrayList; 016 017import javax.swing.JLabel; 018import javax.swing.JTable; 019import javax.swing.UIManager; 020import javax.swing.table.DefaultTableCellRenderer; 021import javax.swing.table.TableCellRenderer; 022 023import org.openstreetmap.josm.data.osm.AbstractPrimitive; 024import org.openstreetmap.josm.data.preferences.BooleanProperty; 025import org.openstreetmap.josm.data.preferences.CachingProperty; 026import org.openstreetmap.josm.data.preferences.NamedColorProperty; 027import org.openstreetmap.josm.tools.I18n; 028import org.openstreetmap.josm.tools.Pair; 029 030/** 031 * Cell renderer of tags table. 032 * @since 6314 033 */ 034public class PropertiesCellRenderer extends DefaultTableCellRenderer { 035 036 private static final CachingProperty<Color> SELECTED_FG 037 = new NamedColorProperty(marktr("Discardable key: selection Foreground"), Color.GRAY).cached(); 038 private static final CachingProperty<Color> SELECTED_BG; 039 private static final CachingProperty<Color> NORMAL_FG 040 = new NamedColorProperty(marktr("Discardable key: foreground"), Color.GRAY).cached(); 041 private static final CachingProperty<Color> NORMAL_BG; 042 private static final CachingProperty<Boolean> DISCARDABLE 043 = new BooleanProperty("display.discardable-keys", false).cached(); 044 045 static { 046 SELECTED_BG = new NamedColorProperty(marktr("Discardable key: selection Background"), 047 Optional.ofNullable(UIManager.getColor("Table.selectionBackground")).orElse(Color.BLUE)).cached(); 048 NORMAL_BG = new NamedColorProperty(marktr("Discardable key: background"), 049 Optional.ofNullable(UIManager.getColor("Table.background")).orElse(Color.WHITE)).cached(); 050 } 051 052 private final Collection<TableCellRenderer> customRenderer = new CopyOnWriteArrayList<>(); 053 054 private static void setColors(Component c, String key, boolean isSelected) { 055 056 if (AbstractPrimitive.getDiscardableKeys().contains(key)) { 057 c.setForeground((isSelected ? SELECTED_FG : NORMAL_FG).get()); 058 c.setBackground((isSelected ? SELECTED_BG : NORMAL_BG).get()); 059 } else { 060 c.setForeground(UIManager.getColor("Table."+(isSelected ? "selectionF" : "f")+"oreground")); 061 c.setBackground(UIManager.getColor("Table."+(isSelected ? "selectionB" : "b")+"ackground")); 062 } 063 } 064 065 @Override 066 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 067 for (TableCellRenderer renderer : customRenderer) { 068 final Component component = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 069 if (component != null) { 070 return component; 071 } 072 } 073 if (value == null) 074 return this; 075 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column); 076 if (c instanceof JLabel) { 077 String str = null; 078 if (value instanceof String) { 079 str = (String) value; 080 } else if (value instanceof Map<?, ?>) { 081 Map<?, ?> v = (Map<?, ?>) value; 082 if (v.size() != 1) { // Multiple values: give user a short summary of the values 083 Integer blankCount; 084 Integer otherCount; 085 if (v.get("") == null) { 086 blankCount = 0; 087 otherCount = v.size(); 088 } else { 089 blankCount = (Integer) v.get(""); 090 otherCount = v.size()-1; 091 } 092 StringBuilder sb = new StringBuilder("<"); 093 if (otherCount == 1) { 094 // Find the non-blank value in the map 095 v.entrySet().stream().filter(entry -> !Objects.equals(entry.getKey(), "")) 096 /* I18n: properties display partial string joined with comma, first is count, second is value */ 097 .findAny().ifPresent(entry -> sb.append(tr("{0} ''{1}''", entry.getValue().toString(), entry.getKey()))); 098 } else { 099 /* I18n: properties display partial string joined with comma */ 100 sb.append(trn("{0} different", "{0} different", otherCount, otherCount)); 101 } 102 if (blankCount > 0) { 103 /* I18n: properties display partial string joined with comma */ 104 sb.append(trn(", {0} unset", ", {0} unset", blankCount, blankCount)); 105 } 106 sb.append('>'); 107 str = sb.toString(); 108 c.setFont(c.getFont().deriveFont(Font.ITALIC)); 109 110 } else { // One value: display the value 111 str = (String) v.entrySet().iterator().next().getKey(); 112 } 113 } 114 boolean knownNameKey = false; 115 if (column == 0 && str != null) { 116 Pair<String, Boolean> label = I18n.getLocalizedLanguageName(str); 117 if (label != null) { 118 knownNameKey = label.b; 119 if (knownNameKey) { 120 str = new StringBuilder("<html><body>").append(str) 121 .append(" <i><").append(label.a).append("></i></body></html>").toString(); 122 } 123 } 124 } 125 ((JLabel) c).putClientProperty("html.disable", knownNameKey ? null : Boolean.TRUE); // Fix #8730 126 ((JLabel) c).setText(str); 127 if (DISCARDABLE.get()) { 128 String key = null; 129 if (column == 0) { 130 key = str; 131 } else if (column == 1) { 132 Object value0 = table.getModel().getValueAt(row, 0); 133 if (value0 instanceof String) { 134 key = (String) value0; 135 } 136 } 137 setColors(c, key, isSelected); 138 } 139 } 140 return c; 141 } 142 143 /** 144 * Adds a custom table cell renderer to render cells of the tags table. 145 * 146 * If the renderer is not capable performing a {@link TableCellRenderer#getTableCellRendererComponent}, 147 * it should return {@code null} to fall back to the 148 * {@link PropertiesCellRenderer#getTableCellRendererComponent default implementation}. 149 * @param renderer the renderer to add 150 * @since 9149 151 */ 152 public void addCustomRenderer(TableCellRenderer renderer) { 153 customRenderer.add(renderer); 154 } 155 156 /** 157 * Removes a custom table cell renderer. 158 * @param renderer the renderer to remove 159 * @since 9149 160 */ 161 public void removeCustomRenderer(TableCellRenderer renderer) { 162 customRenderer.remove(renderer); 163 } 164}