001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.util;
003
004import java.awt.Component;
005
006import javax.swing.JTable;
007import javax.swing.ListSelectionModel;
008import javax.swing.event.ListDataEvent;
009import javax.swing.event.ListSelectionEvent;
010import javax.swing.table.AbstractTableModel;
011import javax.swing.table.TableCellRenderer;
012import javax.swing.table.TableColumn;
013
014import org.openstreetmap.josm.gui.dialogs.IEnabledStateUpdating;
015
016/**
017 * The class that provide common JTable customization methods
018 * @since 5785
019 */
020public final class TableHelper {
021
022    private TableHelper() {
023        // Hide default constructor for utils classes
024    }
025
026    /**
027     * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that
028     * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()}
029     * on every {@link ListSelectionEvent}.
030     *
031     * @param listener  the listener
032     * @param listSelectionModel  the source emitting {@link ListSelectionEvent}s
033     * @since 15226
034     */
035    public static void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) {
036        listSelectionModel.addListSelectionListener(e -> listener.updateEnabledState());
037    }
038
039    /**
040     * Wires <code>listener</code> to <code>listModel</code> in such a way, that
041     * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()}
042     * on every {@link ListDataEvent}.
043     *
044     * @param listener the listener
045     * @param listModel the source emitting {@link ListDataEvent}s
046     * @since 15226
047     */
048    public static void adaptTo(final IEnabledStateUpdating listener, AbstractTableModel listModel) {
049        listModel.addTableModelListener(e -> listener.updateEnabledState());
050    }
051
052    static int getColumnHeaderWidth(JTable tbl, int col) {
053        TableColumn tableColumn = tbl.getColumnModel().getColumn(col);
054        TableCellRenderer renderer = tableColumn.getHeaderRenderer();
055
056        if (renderer == null && tbl.getTableHeader() != null)
057            renderer = tbl.getTableHeader().getDefaultRenderer();
058
059        if (renderer == null)
060            return 0;
061
062        Component c = renderer.getTableCellRendererComponent(tbl, tableColumn.getHeaderValue(), false, false, -1, col);
063        return c.getPreferredSize().width;
064    }
065
066    static int getMaxWidth(JTable tbl, int col) {
067        int maxwidth = getColumnHeaderWidth(tbl, col);
068        for (int row = 0; row < tbl.getRowCount(); row++) {
069            TableCellRenderer tcr = tbl.getCellRenderer(row, col);
070            Object val = tbl.getValueAt(row, col);
071            Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, row, col);
072            maxwidth = Math.max(comp.getPreferredSize().width, maxwidth);
073        }
074        return maxwidth;
075    }
076
077    /**
078     * adjust the preferred width of column col to the maximum preferred width of the cells (including header)
079     * @param tbl table
080     * @param col column index
081     * @param resizable if true, resizing is allowed
082     * @since 15176
083     */
084    public static void adjustColumnWidth(JTable tbl, int col, boolean resizable) {
085        int maxwidth = getMaxWidth(tbl, col);
086        TableColumn column = tbl.getColumnModel().getColumn(col);
087        column.setPreferredWidth(maxwidth);
088        column.setResizable(resizable);
089        if (!resizable) {
090            column.setMaxWidth(maxwidth);
091        }
092    }
093
094    /**
095     * adjust the preferred width of column col to the maximum preferred width of the cells (including header)
096     * requires JTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
097     * @param tbl table
098     * @param col column index
099     * @param maxColumnWidth maximum column width
100     */
101    public static void adjustColumnWidth(JTable tbl, int col, int maxColumnWidth) {
102        int maxwidth = getMaxWidth(tbl, col);
103        tbl.getColumnModel().getColumn(col).setPreferredWidth(Math.min(maxwidth+10, maxColumnWidth));
104    }
105
106    /**
107     * adjust the table's columns to fit their content best
108     * requires JTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
109     * @param tbl table
110     * @since 14476
111     */
112    public static void computeColumnsWidth(JTable tbl) {
113        for (int column = 0; column < tbl.getColumnCount(); column++) {
114            adjustColumnWidth(tbl, column, Integer.MAX_VALUE);
115        }
116    }
117
118    /**
119     * Returns an array of all of the selected indices in the selection model, in increasing order.
120     * Unfortunately this method is not available in OpenJDK before version 11, see
121     * https://bugs.openjdk.java.net/browse/JDK-8199395
122     * Code taken from OpenJDK 11. To be removed when we switch to Java 11 or later.
123     *
124     * @param selectionModel list selection model.
125     *
126     * @return all of the selected indices, in increasing order,
127     *         or an empty array if nothing is selected
128     * @since 15226
129     */
130    public static int[] getSelectedIndices(ListSelectionModel selectionModel) {
131        int iMin = selectionModel.getMinSelectionIndex();
132        int iMax = selectionModel.getMaxSelectionIndex();
133
134        if (iMin < 0 || iMax < 0) {
135            return new int[0];
136        }
137
138        int[] rvTmp = new int[1 + iMax - iMin];
139        int n = 0;
140        for (int i = iMin; i <= iMax; i++) {
141            if (selectionModel.isSelectedIndex(i)) {
142                rvTmp[n++] = i;
143            }
144        }
145        int[] rv = new int[n];
146        System.arraycopy(rvTmp, 0, rv, 0, n);
147        return rv;
148    }
149}