1 package com.jsql.view.swing.table;
2
3 import com.jsql.util.LogLevelUtil;
4 import org.apache.logging.log4j.LogManager;
5 import org.apache.logging.log4j.Logger;
6
7 import javax.swing.*;
8 import javax.swing.event.TableModelEvent;
9 import javax.swing.event.TableModelListener;
10 import javax.swing.table.TableCellRenderer;
11 import javax.swing.table.TableColumn;
12 import javax.swing.table.TableColumnModel;
13 import javax.swing.table.TableModel;
14 import java.awt.*;
15 import java.awt.event.ActionEvent;
16 import java.beans.PropertyChangeEvent;
17 import java.beans.PropertyChangeListener;
18 import java.util.HashMap;
19 import java.util.Map;
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 public class AdjusterTableColumn implements PropertyChangeListener, TableModelListener {
36
37 private static final Logger LOGGER = LogManager.getRootLogger();
38
39 private final JTable tableAdjust;
40 private final int spacing;
41 private boolean isColumnHeaderIncluded;
42 private boolean isColumnDataIncluded;
43 private boolean isOnlyAdjustLarger;
44 private boolean isDynamicAdjustment;
45 private final Map<TableColumn, Integer> columnSizes = new HashMap<>();
46
47
48
49
50 public AdjusterTableColumn(JTable table) {
51 this(table, 6);
52 }
53
54
55
56
57 public AdjusterTableColumn(JTable tableAdjust, int spacing) {
58 this.tableAdjust = tableAdjust;
59 this.spacing = spacing;
60 this.setColumnHeaderIncluded(true);
61 this.setColumnDataIncluded(true);
62 this.setOnlyAdjustLarger(true);
63 this.setDynamicAdjustment(false);
64 this.installActions();
65 }
66
67
68
69
70 public void adjustColumns() {
71 TableColumnModel tcm = this.tableAdjust.getColumnModel();
72 for (var i = 0 ; i < tcm.getColumnCount() ; i++) {
73 this.adjustColumn(i);
74 }
75 }
76
77
78
79
80 public void adjustColumn(final int column) {
81 var tableColumn = this.tableAdjust.getColumnModel().getColumn(column);
82 if (!tableColumn.getResizable()) {
83 return;
84 }
85
86 int columnHeaderWidth = this.getColumnHeaderWidth(column);
87 int columnDataWidth = this.getColumnDataWidth(column);
88 int preferredWidth = Math.max(columnHeaderWidth, columnDataWidth);
89 this.updateTableColumn(column, preferredWidth);
90 }
91
92
93
94
95 private int getColumnHeaderWidth(int column) {
96 if (!this.isColumnHeaderIncluded) {
97 return 0;
98 }
99
100 var tableColumn = this.tableAdjust.getColumnModel().getColumn(column);
101 Object value = tableColumn.getHeaderValue();
102 TableCellRenderer renderer = tableColumn.getHeaderRenderer();
103 if (renderer == null) {
104 renderer = this.tableAdjust.getTableHeader().getDefaultRenderer();
105 }
106
107 var c = renderer.getTableCellRendererComponent(this.tableAdjust, value, false, false, -1, column);
108 return c.getPreferredSize().width;
109 }
110
111
112
113
114
115 private int getColumnDataWidth(int column) {
116 if (!this.isColumnDataIncluded) {
117 return 0;
118 }
119
120 var preferredWidth = 0;
121 int maxWidth = this.tableAdjust.getColumnModel().getColumn(column).getMaxWidth();
122
123 for (var row = 0 ; row < this.tableAdjust.getRowCount() ; row++) {
124 preferredWidth = Math.max(preferredWidth, this.getCellDataWidth(row, column));
125
126 if (preferredWidth >= maxWidth) {
127 break;
128 }
129 }
130 return preferredWidth;
131 }
132
133
134
135
136 private int getCellDataWidth(int row, int column) {
137
138 TableCellRenderer cellRenderer = this.tableAdjust.getCellRenderer(row, column);
139 Component c = this.tableAdjust.prepareRenderer(cellRenderer, row, column);
140 if (c != null) {
141 return c.getPreferredSize().width + this.tableAdjust.getIntercellSpacing().width;
142 } else {
143 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, "Unexpected missing cell, data width set to 0");
144 return 100;
145 }
146 }
147
148
149
150
151 private void updateTableColumn(int column, int width) {
152 final var tableColumn = this.tableAdjust.getColumnModel().getColumn(column);
153 if (!tableColumn.getResizable()) {
154 return;
155 }
156
157 int calculatedWidth = width;
158 calculatedWidth += this.spacing;
159
160 if (this.isOnlyAdjustLarger) {
161 calculatedWidth = Math.max(calculatedWidth, tableColumn.getPreferredWidth());
162 }
163
164 this.columnSizes.put(tableColumn, tableColumn.getWidth());
165 this.tableAdjust.getTableHeader().setResizingColumn(tableColumn);
166 tableColumn.setWidth(calculatedWidth);
167 }
168
169
170
171
172 public void restoreColumns() {
173 TableColumnModel tableColumnModel = this.tableAdjust.getColumnModel();
174 for (var i = 0 ; i < tableColumnModel.getColumnCount() ; i++) {
175 this.restoreColumn(i);
176 }
177 }
178
179
180
181
182 private void restoreColumn(int column) {
183 var tableColumn = this.tableAdjust.getColumnModel().getColumn(column);
184 Integer width = this.columnSizes.get(tableColumn);
185 if (width != null) {
186 this.tableAdjust.getTableHeader().setResizingColumn(tableColumn);
187 tableColumn.setWidth(width);
188 }
189 }
190
191
192
193
194 public void setColumnHeaderIncluded(boolean isColumnHeaderIncluded) {
195 this.isColumnHeaderIncluded = isColumnHeaderIncluded;
196 }
197
198
199
200
201 public void setColumnDataIncluded(boolean isColumnDataIncluded) {
202 this.isColumnDataIncluded = isColumnDataIncluded;
203 }
204
205
206
207
208 public void setOnlyAdjustLarger(boolean isOnlyAdjustLarger) {
209 this.isOnlyAdjustLarger = isOnlyAdjustLarger;
210 }
211
212
213
214
215
216 public void setDynamicAdjustment(boolean isDynamicAdjustment) {
217
218 if (this.isDynamicAdjustment != isDynamicAdjustment) {
219 if (isDynamicAdjustment) {
220 this.tableAdjust.addPropertyChangeListener(this);
221 this.tableAdjust.getModel().addTableModelListener(this);
222 } else {
223 this.tableAdjust.removePropertyChangeListener(this);
224 this.tableAdjust.getModel().removeTableModelListener(this);
225 }
226 }
227 this.isDynamicAdjustment = isDynamicAdjustment;
228 }
229
230
231
232
233 @Override
234 public void propertyChange(PropertyChangeEvent e) {
235
236
237 if ("model".equals(e.getPropertyName())) {
238 TableModel model = (TableModel) e.getOldValue();
239 model.removeTableModelListener(this);
240
241 model = (TableModel) e.getNewValue();
242 model.addTableModelListener(this);
243 this.adjustColumns();
244 }
245 }
246
247
248
249
250 @Override
251 public void tableChanged(TableModelEvent e) {
252 if (!this.isColumnDataIncluded) {
253 return;
254 }
255
256
257 if (e.getType() == TableModelEvent.UPDATE) {
258 int column = this.tableAdjust.convertColumnIndexToView(e.getColumn());
259
260
261 if (this.isOnlyAdjustLarger) {
262 int row = e.getFirstRow();
263 var tableColumn = this.tableAdjust.getColumnModel().getColumn(column);
264 if (tableColumn.getResizable()) {
265 int width = this.getCellDataWidth(row, column);
266 this.updateTableColumn(column, width);
267 }
268 } else {
269 this.adjustColumn(column);
270 }
271 } else {
272 this.adjustColumns();
273 }
274 }
275
276
277
278
279 private void installActions() {
280 this.installColumnAction(true, true, "adjustColumn", "control ADD");
281 this.installColumnAction(false, true, "adjustColumns", "control shift ADD");
282 this.installColumnAction(true, false, "restoreColumn", "control SUBTRACT");
283 this.installColumnAction(false, false, "restoreColumns", "control shift SUBTRACT");
284
285 this.installToggleAction(true, false, "toggleDynamic", "control MULTIPLY");
286 this.installToggleAction(false, true, "toggleLarger", "control DIVIDE");
287 }
288
289
290
291
292 private void installColumnAction(boolean isSelectedColumn, boolean isAdjust, String key, String keyStroke) {
293 Action action = new ColumnAction(isSelectedColumn, isAdjust);
294 var ks = KeyStroke.getKeyStroke(keyStroke);
295
296 this.tableAdjust.getInputMap().put(ks, key);
297 this.tableAdjust.getActionMap().put(key, action);
298 }
299
300
301
302
303 private void installToggleAction(boolean isToggleDynamic, boolean isToggleLarger, String key, String keyStroke) {
304 Action action = new ToggleAction(isToggleDynamic, isToggleLarger);
305 var ks = KeyStroke.getKeyStroke(keyStroke);
306
307 this.tableAdjust.getInputMap().put(ks, key);
308 this.tableAdjust.getActionMap().put(key, action);
309 }
310
311
312
313
314 class ColumnAction extends AbstractAction {
315
316 private final boolean isSelectedColumn;
317 private final boolean isAdjust;
318
319 public ColumnAction(boolean isSelectedColumn, boolean isAdjust) {
320 this.isSelectedColumn = isSelectedColumn;
321 this.isAdjust = isAdjust;
322 }
323
324 @Override
325 public void actionPerformed(ActionEvent e) {
326
327 if (this.isSelectedColumn) {
328 int[] columns = AdjusterTableColumn.this.tableAdjust.getSelectedColumns();
329
330 for (int column: columns) {
331 if (this.isAdjust) {
332 AdjusterTableColumn.this.adjustColumn(column);
333 } else {
334 AdjusterTableColumn.this.restoreColumn(column);
335 }
336 }
337 } else {
338 if (this.isAdjust) {
339 AdjusterTableColumn.this.adjustColumns();
340 } else {
341 AdjusterTableColumn.this.restoreColumns();
342 }
343 }
344 }
345 }
346
347
348
349
350
351 class ToggleAction extends AbstractAction {
352
353 private final boolean isToggleDynamic;
354 private final boolean isToggleLarger;
355
356 public ToggleAction(boolean isToggleDynamic, boolean isToggleLarger) {
357 this.isToggleDynamic = isToggleDynamic;
358 this.isToggleLarger = isToggleLarger;
359 }
360
361 @Override
362 public void actionPerformed(ActionEvent e) {
363 if (this.isToggleDynamic) {
364 AdjusterTableColumn.this.setDynamicAdjustment(!AdjusterTableColumn.this.isDynamicAdjustment);
365 } else if (this.isToggleLarger) {
366 AdjusterTableColumn.this.setOnlyAdjustLarger(!AdjusterTableColumn.this.isOnlyAdjustLarger);
367 }
368 }
369 }
370 }