Statistics
| Revision:

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
}