gvsig-tools / org.gvsig.tools / library / trunk / org.gvsig.tools / org.gvsig.tools.swing / org.gvsig.tools.swing.impl / src / main / java / org / gvsig / tools / swing / impl / TableColumnAdjusterImpl.java @ 2188
History | View | Annotate | Download (15.4 KB)
1 |
/**
|
---|---|
2 |
* gvSIG. Desktop Geographic Information System.
|
3 |
*
|
4 |
* Copyright (C) 2007-2020 gvSIG Association.
|
5 |
*
|
6 |
* This program is free software; you can redistribute it and/or
|
7 |
* modify it under the terms of the GNU General Public License
|
8 |
* as published by the Free Software Foundation; either version 3
|
9 |
* of the License, or (at your option) any later version.
|
10 |
*
|
11 |
* This program is distributed in the hope that it will be useful,
|
12 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14 |
* GNU General Public License for more details.
|
15 |
*
|
16 |
* You should have received a copy of the GNU General Public License
|
17 |
* along with this program; if not, write to the Free Software
|
18 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19 |
* MA 02110-1301, USA.
|
20 |
*
|
21 |
* For any additional information, do not hesitate to contact us
|
22 |
* at info AT gvsig.com, or visit our website www.gvsig.com.
|
23 |
*/
|
24 |
package org.gvsig.tools.swing.impl; |
25 |
|
26 |
/*
|
27 |
* Based on portions of code from "Table Column Adjuster"
|
28 |
* Posted by Rob Camick on November 10, 2008 in "Java Tips Weblog"
|
29 |
* https://tips4java.wordpress.com/2008/11/10/table-column-adjuster/
|
30 |
*/
|
31 |
import org.gvsig.tools.swing.api.TableColumnAdjuster; |
32 |
import java.awt.Component; |
33 |
import java.awt.event.ActionEvent; |
34 |
import java.awt.event.ComponentAdapter; |
35 |
import java.awt.event.ComponentEvent; |
36 |
import java.awt.event.ComponentListener; |
37 |
import java.beans.PropertyChangeEvent; |
38 |
import java.beans.PropertyChangeListener; |
39 |
import java.util.HashMap; |
40 |
import java.util.Map; |
41 |
import javax.swing.AbstractAction; |
42 |
import javax.swing.Action; |
43 |
import javax.swing.JScrollPane; |
44 |
import javax.swing.JTable; |
45 |
import javax.swing.JViewport; |
46 |
import javax.swing.KeyStroke; |
47 |
import javax.swing.SwingUtilities; |
48 |
import javax.swing.event.TableModelEvent; |
49 |
import javax.swing.event.TableModelListener; |
50 |
import javax.swing.table.TableCellRenderer; |
51 |
import javax.swing.table.TableColumn; |
52 |
import javax.swing.table.TableColumnModel; |
53 |
import javax.swing.table.TableModel; |
54 |
|
55 |
|
56 |
/*
|
57 |
* Class to manage the widths of colunmns in a table.
|
58 |
*
|
59 |
* Various properties control how the width of the column is calculated.
|
60 |
* Another property controls whether column width calculation should be dynamic.
|
61 |
* Finally, various Actions will be added to the table to allow the user
|
62 |
* to customize the functionality.
|
63 |
*
|
64 |
* This class was designed to be used with tables that use an auto resize mode
|
65 |
* of AUTO_RESIZE_OFF. With all other modes you are constrained as the width
|
66 |
* of the columns must fit inside the table. So if you increase one column, one
|
67 |
* or more of the other columns must decrease. Because of this the resize mode
|
68 |
* of RESIZE_ALL_COLUMNS will work the best.
|
69 |
*
|
70 |
* Usage:
|
71 |
*
|
72 |
* TableColumnAdjuster.install(table);
|
73 |
*
|
74 |
*/
|
75 |
public class TableColumnAdjusterImpl implements PropertyChangeListener, TableModelListener, TableColumnAdjuster { |
76 |
|
77 |
private JTable table; |
78 |
private int spacing; |
79 |
private boolean isColumnHeaderIncluded; |
80 |
private boolean isColumnDataIncluded; |
81 |
private boolean isOnlyAdjustLarger; |
82 |
private boolean isDynamicAdjustment; |
83 |
private Map<TableColumn, Integer> columnSizes; |
84 |
private JScrollPane scrollPane; |
85 |
private int sugestedLastColumnWidth; |
86 |
private ComponentListener lastColumnAdjuster; |
87 |
|
88 |
/*
|
89 |
* Specify the table and use default spacing
|
90 |
*/
|
91 |
protected TableColumnAdjusterImpl(JTable table) { |
92 |
this(table, null, 6); |
93 |
} |
94 |
|
95 |
/*
|
96 |
* Specify the table and spacing
|
97 |
*/
|
98 |
@SuppressWarnings("OverridableMethodCallInConstructor") |
99 |
protected TableColumnAdjusterImpl(JTable table, JScrollPane scrollPane, int spacing) { |
100 |
this.scrollPane = scrollPane;
|
101 |
this.lastColumnAdjuster = null; |
102 |
this.sugestedLastColumnWidth = 0; |
103 |
this.columnSizes = new HashMap<>(); |
104 |
this.table = table;
|
105 |
this.spacing = spacing;
|
106 |
if( this.scrollPane==null ) { |
107 |
this.scrollPane = this.findScrollPane(); |
108 |
} |
109 |
if( this.scrollPane!=null) { |
110 |
this.lastColumnAdjuster = new ComponentAdapter() { |
111 |
@Override
|
112 |
public void componentResized(ComponentEvent e) { |
113 |
updateLastColumnWidth(); |
114 |
} |
115 |
}; |
116 |
this.scrollPane.addComponentListener(this.lastColumnAdjuster); |
117 |
} |
118 |
setColumnHeaderIncluded(true);
|
119 |
setColumnDataIncluded(false);
|
120 |
setOnlyAdjustLarger(false);
|
121 |
setDynamicAdjustment(false);
|
122 |
// installActions();
|
123 |
} |
124 |
|
125 |
public static TableColumnAdjusterImpl install(JTable table) { |
126 |
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
127 |
TableColumnAdjusterImpl tca = new TableColumnAdjusterImpl(table);
|
128 |
table.addPropertyChangeListener(tca); |
129 |
tca.adjustColumns(); |
130 |
return tca;
|
131 |
} |
132 |
|
133 |
@Override
|
134 |
public void uninstall() { |
135 |
this.setDynamicAdjustment(false); |
136 |
if( this.lastColumnAdjuster!=null ) { |
137 |
this.table.removeComponentListener(lastColumnAdjuster);
|
138 |
} |
139 |
table.removePropertyChangeListener(this);
|
140 |
} |
141 |
|
142 |
private JScrollPane findScrollPane() { |
143 |
if (table.getParent() instanceof JViewport) { |
144 |
JViewport viewport = (JViewport) table.getParent(); |
145 |
if (viewport.getParent() instanceof JScrollPane) { |
146 |
JScrollPane sp = (JScrollPane) viewport.getParent(); |
147 |
return sp;
|
148 |
} |
149 |
} |
150 |
return null; |
151 |
} |
152 |
|
153 |
private void updateLastColumnWidth() { |
154 |
TableColumnModel tcm = table.getColumnModel();
|
155 |
if( tcm.getColumnCount()<1 ) { |
156 |
return;
|
157 |
} |
158 |
if (this.sugestedLastColumnWidth < 1 ) { |
159 |
this.sugestedLastColumnWidth = tcm.getColumn(tcm.getColumnCount() - 1).getWidth(); |
160 |
} |
161 |
|
162 |
int with = 0; |
163 |
int lastColumn = tcm.getColumnCount() - 1; |
164 |
for (int i = 0; i < lastColumn; i++) { |
165 |
with += tcm.getColumn(i).getWidth(); |
166 |
} |
167 |
int visibleWith = scrollPane.getWidth();
|
168 |
if (scrollPane.getVerticalScrollBar().isVisible()) {
|
169 |
visibleWith -= scrollPane.getVerticalScrollBar().getWidth(); |
170 |
} |
171 |
if (visibleWith > with + this.sugestedLastColumnWidth) { |
172 |
int lastColumnWidth = visibleWith - with;
|
173 |
tcm.getColumn(lastColumn).setWidth(lastColumnWidth); |
174 |
} else {
|
175 |
tcm.getColumn(lastColumn).setWidth(this.sugestedLastColumnWidth);
|
176 |
} |
177 |
table.getTableHeader().setResizingColumn(tcm.getColumn(lastColumn)); |
178 |
} |
179 |
|
180 |
/*
|
181 |
* Adjust the widths of all the columns in the table
|
182 |
*/
|
183 |
@Override
|
184 |
public void adjustColumns() { |
185 |
TableColumnModel tcm = table.getColumnModel();
|
186 |
|
187 |
for (int i = 0; i < tcm.getColumnCount(); i++) { |
188 |
adjustColumn(i); |
189 |
} |
190 |
} |
191 |
|
192 |
/*
|
193 |
* Adjust the width of the specified column in the table
|
194 |
*/
|
195 |
@Override
|
196 |
public void adjustColumn(final int column) { |
197 |
TableColumn tableColumn = table.getColumnModel().getColumn(column);
|
198 |
|
199 |
if (!tableColumn.getResizable()) {
|
200 |
return;
|
201 |
} |
202 |
|
203 |
int columnHeaderWidth = getColumnHeaderWidth(column);
|
204 |
int columnDataWidth = getColumnDataWidth(column);
|
205 |
int preferredWidth = Math.max(columnHeaderWidth, columnDataWidth); |
206 |
|
207 |
updateTableColumn(column, preferredWidth); |
208 |
} |
209 |
|
210 |
/*
|
211 |
* Calculated the width based on the column name
|
212 |
*/
|
213 |
private int getColumnHeaderWidth(int column) { |
214 |
if (!isColumnHeaderIncluded) {
|
215 |
return 0; |
216 |
} |
217 |
|
218 |
TableColumn tableColumn = table.getColumnModel().getColumn(column);
|
219 |
Object value = tableColumn.getHeaderValue();
|
220 |
TableCellRenderer renderer = tableColumn.getHeaderRenderer();
|
221 |
|
222 |
if (renderer == null) { |
223 |
renderer = table.getTableHeader().getDefaultRenderer(); |
224 |
} |
225 |
|
226 |
Component c = renderer.getTableCellRendererComponent(table, value, false, false, -1, column); |
227 |
return c.getPreferredSize().width;
|
228 |
} |
229 |
|
230 |
/*
|
231 |
* Calculate the width based on the widest cell renderer for the
|
232 |
* given column.
|
233 |
*/
|
234 |
private int getColumnDataWidth(int column) { |
235 |
if (!isColumnDataIncluded) {
|
236 |
return 0; |
237 |
} |
238 |
|
239 |
int preferredWidth = 0; |
240 |
int maxWidth = table.getColumnModel().getColumn(column).getMaxWidth();
|
241 |
|
242 |
for (int row = 0; row < table.getRowCount(); row++) { |
243 |
preferredWidth = Math.max(preferredWidth, getCellDataWidth(row, column));
|
244 |
|
245 |
// We've exceeded the maximum width, no need to check other rows
|
246 |
if (preferredWidth >= maxWidth) {
|
247 |
break;
|
248 |
} |
249 |
} |
250 |
|
251 |
return preferredWidth;
|
252 |
} |
253 |
|
254 |
/*
|
255 |
* Get the preferred width for the specified cell
|
256 |
*/
|
257 |
private int getCellDataWidth(int row, int column) { |
258 |
// Inovke the renderer for the cell to calculate the preferred width
|
259 |
|
260 |
TableCellRenderer cellRenderer = table.getCellRenderer(row, column);
|
261 |
Component c = table.prepareRenderer(cellRenderer, row, column);
|
262 |
int width = c.getPreferredSize().width + table.getIntercellSpacing().width;
|
263 |
|
264 |
return width;
|
265 |
} |
266 |
|
267 |
/*
|
268 |
* Update the TableColumn with the newly calculated width
|
269 |
*/
|
270 |
private void updateTableColumn(int column, int width) { |
271 |
final TableColumn tableColumn = table.getColumnModel().getColumn(column); |
272 |
|
273 |
if (!tableColumn.getResizable()) {
|
274 |
return;
|
275 |
} |
276 |
|
277 |
width += spacing; |
278 |
|
279 |
// Don't shrink the column width
|
280 |
if (isOnlyAdjustLarger) {
|
281 |
width = Math.max(width, tableColumn.getPreferredWidth());
|
282 |
} |
283 |
|
284 |
columnSizes.put(tableColumn, tableColumn.getWidth()); |
285 |
|
286 |
table.getTableHeader().setResizingColumn(tableColumn); |
287 |
tableColumn.setWidth(width); |
288 |
} |
289 |
|
290 |
/*
|
291 |
* Restore the widths of the columns in the table to its previous width
|
292 |
*/
|
293 |
@Override
|
294 |
public void restoreColumns() { |
295 |
TableColumnModel tcm = table.getColumnModel();
|
296 |
|
297 |
for (int i = 0; i < tcm.getColumnCount(); i++) { |
298 |
restoreColumn(i); |
299 |
} |
300 |
} |
301 |
|
302 |
/*
|
303 |
* Restore the width of the specified column to its previous width
|
304 |
*/
|
305 |
private void restoreColumn(int column) { |
306 |
TableColumn tableColumn = table.getColumnModel().getColumn(column);
|
307 |
Integer width = columnSizes.get(tableColumn);
|
308 |
|
309 |
if (width != null) { |
310 |
table.getTableHeader().setResizingColumn(tableColumn); |
311 |
tableColumn.setWidth(width); |
312 |
} |
313 |
} |
314 |
|
315 |
/*
|
316 |
* Indicates whether to include the header in the width calculation
|
317 |
*/
|
318 |
@Override
|
319 |
public TableColumnAdjusterImpl setColumnHeaderIncluded(boolean isColumnHeaderIncluded) { |
320 |
this.isColumnHeaderIncluded = isColumnHeaderIncluded;
|
321 |
return this; |
322 |
} |
323 |
|
324 |
/*
|
325 |
* Indicates whether to include the model data in the width calculation
|
326 |
*/
|
327 |
@Override
|
328 |
public TableColumnAdjusterImpl setColumnDataIncluded(boolean isColumnDataIncluded) { |
329 |
this.isColumnDataIncluded = isColumnDataIncluded;
|
330 |
return this; |
331 |
} |
332 |
|
333 |
/*
|
334 |
* Indicates whether columns can only be increased in size
|
335 |
*/
|
336 |
@Override
|
337 |
public TableColumnAdjusterImpl setOnlyAdjustLarger(boolean isOnlyAdjustLarger) { |
338 |
this.isOnlyAdjustLarger = isOnlyAdjustLarger;
|
339 |
return this; |
340 |
} |
341 |
|
342 |
/*
|
343 |
* Indicate whether changes to the model should cause the width to be
|
344 |
* dynamically recalculated.
|
345 |
*/
|
346 |
@Override
|
347 |
public TableColumnAdjusterImpl setDynamicAdjustment(boolean isDynamicAdjustment) { |
348 |
// May need to add or remove the TableModelListener when changed
|
349 |
|
350 |
if (this.isDynamicAdjustment != isDynamicAdjustment) { |
351 |
if (isDynamicAdjustment) {
|
352 |
table.getModel().addTableModelListener(this);
|
353 |
} else {
|
354 |
table.getModel().removeTableModelListener(this);
|
355 |
} |
356 |
} |
357 |
|
358 |
this.isDynamicAdjustment = isDynamicAdjustment;
|
359 |
return this; |
360 |
} |
361 |
|
362 |
//
|
363 |
// Implement the PropertyChangeListener
|
364 |
//
|
365 |
@Override
|
366 |
public void propertyChange(PropertyChangeEvent e) { |
367 |
// When the TableModel changes we need to update the listeners
|
368 |
// and column widths
|
369 |
|
370 |
if ("model".equals(e.getPropertyName())) { |
371 |
if (isDynamicAdjustment) {
|
372 |
TableModel model = (TableModel) e.getOldValue(); |
373 |
model.removeTableModelListener(this);
|
374 |
|
375 |
model = (TableModel) e.getNewValue();
|
376 |
model.addTableModelListener(this);
|
377 |
} |
378 |
adjustColumns(); |
379 |
} |
380 |
} |
381 |
|
382 |
//
|
383 |
// Implement the TableModelListener
|
384 |
//
|
385 |
@Override
|
386 |
public void tableChanged(TableModelEvent e) { |
387 |
if (!isColumnDataIncluded) {
|
388 |
return;
|
389 |
} |
390 |
|
391 |
// Needed when table is sorted.
|
392 |
SwingUtilities.invokeLater(() -> {
|
393 |
// A cell has been updated
|
394 |
|
395 |
int column = table.convertColumnIndexToView(e.getColumn());
|
396 |
|
397 |
if (e.getType() == TableModelEvent.UPDATE && column != -1) { |
398 |
// Only need to worry about an increase in width for this cell
|
399 |
|
400 |
if (isOnlyAdjustLarger) {
|
401 |
int row = e.getFirstRow();
|
402 |
TableColumn tableColumn = table.getColumnModel().getColumn(column);
|
403 |
|
404 |
if (tableColumn.getResizable()) {
|
405 |
int width = getCellDataWidth(row, column);
|
406 |
updateTableColumn(column, width); |
407 |
} |
408 |
} // Could be an increase of decrease so check all rows
|
409 |
else {
|
410 |
adjustColumn(column); |
411 |
} |
412 |
} // The update affected more than one column so adjust all columns
|
413 |
else {
|
414 |
adjustColumns(); |
415 |
} |
416 |
}); |
417 |
} |
418 |
|
419 |
/*
|
420 |
* Install Actions to give user control of certain functionality.
|
421 |
*/
|
422 |
private void installActions() { |
423 |
installColumnAction(true, true, "adjustColumn", "control ADD"); |
424 |
installColumnAction(false, true, "adjustColumns", "control shift ADD"); |
425 |
installColumnAction(true, false, "restoreColumn", "control SUBTRACT"); |
426 |
installColumnAction(false, false, "restoreColumns", "control shift SUBTRACT"); |
427 |
|
428 |
installToggleAction(true, false, "toggleDynamic", "control MULTIPLY"); |
429 |
installToggleAction(false, true, "toggleLarger", "control DIVIDE"); |
430 |
} |
431 |
|
432 |
/*
|
433 |
* Update the input and action maps with a new ColumnAction
|
434 |
*/
|
435 |
private void installColumnAction( |
436 |
boolean isSelectedColumn, boolean isAdjust, String key, String keyStroke) { |
437 |
Action action = new ColumnAction(isSelectedColumn, isAdjust); |
438 |
KeyStroke ks = KeyStroke.getKeyStroke(keyStroke); |
439 |
table.getInputMap().put(ks, key); |
440 |
table.getActionMap().put(key, action); |
441 |
} |
442 |
|
443 |
/*
|
444 |
* Update the input and action maps with new ToggleAction
|
445 |
*/
|
446 |
private void installToggleAction( |
447 |
boolean isToggleDynamic, boolean isToggleLarger, String key, String keyStroke) { |
448 |
Action action = new ToggleAction(isToggleDynamic, isToggleLarger); |
449 |
KeyStroke ks = KeyStroke.getKeyStroke(keyStroke); |
450 |
table.getInputMap().put(ks, key); |
451 |
table.getActionMap().put(key, action); |
452 |
} |
453 |
|
454 |
/*
|
455 |
* Action to adjust or restore the width of a single column or all columns
|
456 |
*/
|
457 |
class ColumnAction extends AbstractAction { |
458 |
|
459 |
private final boolean isSelectedColumn; |
460 |
private final boolean isAdjust; |
461 |
|
462 |
public ColumnAction(boolean isSelectedColumn, boolean isAdjust) { |
463 |
this.isSelectedColumn = isSelectedColumn;
|
464 |
this.isAdjust = isAdjust;
|
465 |
} |
466 |
|
467 |
@Override
|
468 |
public void actionPerformed(ActionEvent e) { |
469 |
// Handle selected column(s) width change actions
|
470 |
|
471 |
if (isSelectedColumn) {
|
472 |
int[] columns = table.getSelectedColumns(); |
473 |
|
474 |
for (int i = 0; i < columns.length; i++) { |
475 |
if (isAdjust) {
|
476 |
adjustColumn(columns[i]); |
477 |
} else {
|
478 |
restoreColumn(columns[i]); |
479 |
} |
480 |
} |
481 |
} else {
|
482 |
if (isAdjust) {
|
483 |
adjustColumns(); |
484 |
} else {
|
485 |
restoreColumns(); |
486 |
} |
487 |
} |
488 |
} |
489 |
} |
490 |
|
491 |
/*
|
492 |
* Toggle properties of the TableColumnAdjuster so the user can
|
493 |
* customize the functionality to their preferences
|
494 |
*/
|
495 |
class ToggleAction extends AbstractAction { |
496 |
|
497 |
private final boolean isToggleDynamic; |
498 |
private final boolean isToggleLarger; |
499 |
|
500 |
public ToggleAction(boolean isToggleDynamic, boolean isToggleLarger) { |
501 |
this.isToggleDynamic = isToggleDynamic;
|
502 |
this.isToggleLarger = isToggleLarger;
|
503 |
} |
504 |
|
505 |
@Override
|
506 |
@SuppressWarnings("UnnecessaryReturnStatement") |
507 |
public void actionPerformed(ActionEvent e) { |
508 |
if (isToggleDynamic) {
|
509 |
setDynamicAdjustment(!isDynamicAdjustment); |
510 |
return;
|
511 |
} |
512 |
|
513 |
if (isToggleLarger) {
|
514 |
setOnlyAdjustLarger(!isOnlyAdjustLarger); |
515 |
return;
|
516 |
} |
517 |
} |
518 |
} |
519 |
} |