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.concurrent.CopyOnWriteArrayList;
015
016import javax.swing.JLabel;
017import javax.swing.JTable;
018import javax.swing.UIDefaults;
019import javax.swing.table.DefaultTableCellRenderer;
020import javax.swing.table.TableCellRenderer;
021
022import org.openstreetmap.josm.Main;
023import org.openstreetmap.josm.data.osm.OsmPrimitive;
024
025/**
026 * Cell renderer of tags table.
027 * @since 6314
028 */
029public class PropertiesCellRenderer extends DefaultTableCellRenderer {
030
031    private final Collection<TableCellRenderer> customRenderer = new CopyOnWriteArrayList<>();
032
033    private void setColors(Component c, String key, boolean isSelected) {
034        UIDefaults defaults = javax.swing.UIManager.getDefaults();
035        if (OsmPrimitive.getDiscardableKeys().contains(key)) {
036            if (isSelected) {
037                c.setForeground(Main.pref.getColor(marktr("Discardable key: selection Foreground"), Color.GRAY));
038                c.setBackground(Main.pref.getColor(marktr("Discardable key: selection Background"),
039                        defaults.getColor("Table.selectionBackground")));
040            } else {
041                c.setForeground(Main.pref.getColor(marktr("Discardable key: foreground"), Color.GRAY));
042                c.setBackground(Main.pref.getColor(marktr("Discardable key: background"), defaults.getColor("Table.background")));
043            }
044        } else {
045            c.setForeground(defaults.getColor("Table."+(isSelected ? "selectionF" : "f")+"oreground"));
046            c.setBackground(defaults.getColor("Table."+(isSelected ? "selectionB" : "b")+"ackground"));
047        }
048    }
049
050    @Override
051    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
052        for (TableCellRenderer renderer : customRenderer) {
053            final Component component = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
054            if (component != null) {
055                return component;
056            }
057        }
058        Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
059        if (value == null)
060            return this;
061        if (c instanceof JLabel) {
062            String str = null;
063            if (value instanceof String) {
064                str = (String) value;
065            } else if (value instanceof Map<?, ?>) {
066                Map<?, ?> v = (Map<?, ?>) value;
067                if (v.size() != 1) {    // Multiple values: give user a short summary of the values
068                    Integer blankCount;
069                    Integer otherCount;
070                    if (v.get("") == null) {
071                        blankCount = 0;
072                        otherCount = v.size();
073                    } else {
074                        blankCount = (Integer) v.get("");
075                        otherCount = v.size()-1;
076                    }
077                    StringBuilder sb = new StringBuilder("<");
078                    if (otherCount == 1) {
079                        for (Map.Entry<?, ?> entry : v.entrySet()) { // Find the non-blank value in the map
080                            if (!Objects.equals(entry.getKey(), "")) {
081                                /* I18n: properties display partial string joined with comma, frst is count, second is value */
082                                sb.append(tr("{0} ''{1}''", entry.getValue().toString(), entry.getKey()));
083                            }
084                        }
085                    } else {
086                        /* I18n: properties display partial string joined with comma */
087                        sb.append(trn("{0} different", "{0} different", otherCount, otherCount));
088                    }
089                    if (blankCount > 0) {
090                        /* I18n: properties display partial string joined with comma */
091                        sb.append(trn(", {0} unset", ", {0} unset", blankCount, blankCount));
092                    }
093                    sb.append('>');
094                    str = sb.toString();
095                    c.setFont(c.getFont().deriveFont(Font.ITALIC));
096
097                } else {                // One value: display the value
098                    final Map.Entry<?, ?> entry = v.entrySet().iterator().next();
099                    str = (String) entry.getKey();
100                }
101            }
102            ((JLabel) c).putClientProperty("html.disable", Boolean.TRUE); // Fix #8730
103            ((JLabel) c).setText(str);
104            if (Main.pref.getBoolean("display.discardable-keys", false)) {
105                String key = null;
106                if (column == 0) {
107                    key = str;
108                } else if (column == 1) {
109                    Object value0 = table.getModel().getValueAt(row, 0);
110                    if (value0 instanceof String) {
111                        key = (String) value0;
112                    }
113                }
114                setColors(c, key, isSelected);
115            }
116        }
117        return c;
118    }
119
120    /**
121     * Adds a custom table cell renderer to render cells of the tags table.
122     *
123     * If the renderer is not capable performing a {@link TableCellRenderer#getTableCellRendererComponent},
124     * it should return {@code null} to fall back to the
125     * {@link PropertiesCellRenderer#getTableCellRendererComponent default implementation}.
126     * @param renderer the renderer to add
127     * @since 9149
128     */
129    public void addCustomRenderer(TableCellRenderer renderer) {
130        customRenderer.add(renderer);
131    }
132
133    /**
134     * Removes a custom table cell renderer.
135     * @param renderer the renderer to remove
136     * @since 9149
137     */
138    public void removeCustomRenderer(TableCellRenderer renderer) {
139        customRenderer.remove(renderer);
140    }
141}