001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.util; 003 004import java.awt.Component; 005import java.awt.Font; 006import java.util.stream.IntStream; 007 008import javax.swing.JTable; 009import javax.swing.ListSelectionModel; 010import javax.swing.event.ListDataEvent; 011import javax.swing.event.ListSelectionEvent; 012import javax.swing.table.AbstractTableModel; 013import javax.swing.table.TableCellRenderer; 014import javax.swing.table.TableColumn; 015 016import org.openstreetmap.josm.gui.dialogs.IEnabledStateUpdating; 017import org.openstreetmap.josm.spi.preferences.Config; 018 019/** 020 * The class that provide common JTable customization methods 021 * @since 5785 022 */ 023public final class TableHelper { 024 025 private TableHelper() { 026 // Hide default constructor for utils classes 027 } 028 029 /** 030 * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that 031 * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()} 032 * on every {@link ListSelectionEvent}. 033 * 034 * @param listener the listener 035 * @param listSelectionModel the source emitting {@link ListSelectionEvent}s 036 * @since 15226 037 */ 038 public static void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) { 039 listSelectionModel.addListSelectionListener(e -> listener.updateEnabledState()); 040 } 041 042 /** 043 * Wires <code>listener</code> to <code>listModel</code> in such a way, that 044 * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()} 045 * on every {@link ListDataEvent}. 046 * 047 * @param listener the listener 048 * @param listModel the source emitting {@link ListDataEvent}s 049 * @since 15226 050 */ 051 public static void adaptTo(final IEnabledStateUpdating listener, AbstractTableModel listModel) { 052 listModel.addTableModelListener(e -> listener.updateEnabledState()); 053 } 054 055 static int getColumnHeaderWidth(JTable tbl, int col) { 056 TableColumn tableColumn = tbl.getColumnModel().getColumn(col); 057 TableCellRenderer renderer = tableColumn.getHeaderRenderer(); 058 059 if (renderer == null && tbl.getTableHeader() != null) 060 renderer = tbl.getTableHeader().getDefaultRenderer(); 061 062 if (renderer == null) 063 return 0; 064 065 Component c = renderer.getTableCellRendererComponent(tbl, tableColumn.getHeaderValue(), false, false, -1, col); 066 return c.getPreferredSize().width; 067 } 068 069 static int getMaxWidth(JTable tbl, int col) { 070 int maxwidth = getColumnHeaderWidth(tbl, col); 071 for (int row = 0; row < tbl.getRowCount(); row++) { 072 TableCellRenderer tcr = tbl.getCellRenderer(row, col); 073 Object val = tbl.getValueAt(row, col); 074 Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, row, col); 075 maxwidth = Math.max(comp.getPreferredSize().width, maxwidth); 076 } 077 return maxwidth; 078 } 079 080 /** 081 * adjust the preferred width of column col to the maximum preferred width of the cells (including header) 082 * @param tbl table 083 * @param col column index 084 * @param resizable if true, resizing is allowed 085 * @since 15176 086 */ 087 public static void adjustColumnWidth(JTable tbl, int col, boolean resizable) { 088 int maxwidth = getMaxWidth(tbl, col); 089 TableColumn column = tbl.getColumnModel().getColumn(col); 090 column.setPreferredWidth(maxwidth); 091 column.setResizable(resizable); 092 if (!resizable) { 093 column.setMaxWidth(maxwidth); 094 } 095 } 096 097 /** 098 * adjust the preferred width of column col to the maximum preferred width of the cells (including header) 099 * requires JTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 100 * @param tbl table 101 * @param col column index 102 * @param maxColumnWidth maximum column width 103 */ 104 public static void adjustColumnWidth(JTable tbl, int col, int maxColumnWidth) { 105 int maxwidth = getMaxWidth(tbl, col); 106 tbl.getColumnModel().getColumn(col).setPreferredWidth(Math.min(maxwidth+10, maxColumnWidth)); 107 } 108 109 /** 110 * adjust the table's columns to fit their content best 111 * requires JTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 112 * @param tbl table 113 * @since 14476 114 */ 115 public static void computeColumnsWidth(JTable tbl) { 116 for (int column = 0; column < tbl.getColumnCount(); column++) { 117 adjustColumnWidth(tbl, column, Integer.MAX_VALUE); 118 } 119 } 120 121 /** 122 * Returns an array of all of the selected indices in the selection model, in increasing order. 123 * Unfortunately this method is not available in OpenJDK before version 11, see 124 * https://bugs.openjdk.java.net/browse/JDK-8199395 125 * 126 * To be removed when we switch to Java 11 or later. 127 * 128 * @param selectionModel list selection model. 129 * 130 * @return all of the selected indices, in increasing order, 131 * or an empty array if nothing is selected 132 * @see #selectedIndices(ListSelectionModel) 133 * @since 15226 134 */ 135 public static int[] getSelectedIndices(ListSelectionModel selectionModel) { 136 return selectedIndices(selectionModel).toArray(); 137 } 138 139 /** 140 * Returns a stream of all of the selected indices in the selection model, in increasing order. 141 * 142 * @param selectionModel list selection model. 143 * 144 * @return all of the selected indices, in increasing order, 145 * or an empty stream if nothing is selected 146 * @since 17773 147 */ 148 public static IntStream selectedIndices(ListSelectionModel selectionModel) { 149 if (selectionModel.isSelectionEmpty()) { 150 return IntStream.empty(); 151 } 152 return IntStream.rangeClosed(selectionModel.getMinSelectionIndex(), selectionModel.getMaxSelectionIndex()) 153 .filter(selectionModel::isSelectedIndex); 154 } 155 156 /** 157 * Selects the given indices in the selection model 158 * @param selectionModel list selection model. 159 * @param indices the indices to select 160 * @see ListSelectionModel#addSelectionInterval(int, int) 161 * @since 16601 162 */ 163 public static void setSelectedIndices(ListSelectionModel selectionModel, IntStream indices) { 164 selectionModel.setValueIsAdjusting(true); 165 selectionModel.clearSelection(); 166 indices.filter(i -> i >= 0).forEach(i -> selectionModel.addSelectionInterval(i, i)); 167 selectionModel.setValueIsAdjusting(false); 168 } 169 170 /** 171 * Sets the table font size based on the font scaling from the preferences 172 * @param table the table 173 * @param parent the parent component used for determining the preference key 174 * @see JTable#setFont(Font) 175 * @see JTable#setRowHeight(int) 176 */ 177 public static void setFont(JTable table, Class<? extends Component> parent) { 178 double fontFactor = Config.getPref().getDouble("gui.scale.table.font", 179 Config.getPref().getDouble("gui.scale.table." + parent.getSimpleName() + ".font", 1.0)); 180 if (fontFactor == 1.0) { 181 return; 182 } 183 Font font = table.getFont(); 184 table.setFont(font.deriveFont((float) (font.getSize2D() * fontFactor))); 185 // need to setRowHeight, see comment in javax.swing.plaf.basic.BasicTableUI.installDefaults 186 table.setRowHeight((int) (table.getRowHeight() * fontFactor)); 187 } 188}