Ticket #14666: 0001-Key-column-minimal-possile-width.patch

File 0001-Key-column-minimal-possile-width.patch, 16.3 KB (added by bagage, 8 years ago)

patch proposal

  • new file src/org/openstreetmap/josm/gui/TableColumnAdjuster.java

    From 24cbf8480d4f43ea180325b5274c066f2b92aace Mon Sep 17 00:00:00 2001
    From: Gautier Pelloux-Prayer <gautier+git@damsy.net>
    Date: Wed, 19 Apr 2017 19:34:58 +0200
    Subject: [PATCH] Key column minimal possile width
    
    ---
     .../josm/gui/TableColumnAdjuster.java              | 428 +++++++++++++++++++++
     .../gui/dialogs/properties/PropertiesDialog.java   |  12 +
     2 files changed, 440 insertions(+)
     create mode 100644 src/org/openstreetmap/josm/gui/TableColumnAdjuster.java
    
    diff --git a/src/org/openstreetmap/josm/gui/TableColumnAdjuster.java b/src/org/openstreetmap/josm/gui/TableColumnAdjuster.java
    new file mode 100644
    index 000000000..35f7b029d
    - +  
     1// from https://tips4java.wordpress.com/2008/11/10/table-column-adjuster/
     2// license: the “About Page” says you are free to use/modify the code as you wish at your own risk.
     3package org.openstreetmap.josm.gui;
     4
     5import java.awt.*;
     6import java.awt.event.*;
     7import java.beans.*;
     8import java.util.*;
     9import javax.swing.*;
     10import javax.swing.event.*;
     11import javax.swing.table.*;
     12
     13/*
     14 *  Class to manage the widths of columns in a table.
     15 *
     16 *  Various properties control how the width of the column is calculated.
     17 *  Another property controls whether column width calculation should be dynamic.
     18 *  Finally, various Actions will be added to the table to allow the user
     19 *  to customize the functionality.
     20 *
     21 *  This class was designed to be used with tables that use an auto resize mode
     22 *  of AUTO_RESIZE_OFF. With all other modes you are constrained as the width
     23 *  of the columns must fit inside the table. So if you increase one column, one
     24 *  or more of the other columns must decrease. Because of this the resize mode
     25 *  of RESIZE_ALL_COLUMNS will work the best.
     26 */
     27public class TableColumnAdjuster implements PropertyChangeListener, TableModelListener
     28{
     29    private JTable table;
     30    private int spacing;
     31    private boolean isColumnHeaderIncluded;
     32    private boolean isColumnDataIncluded;
     33    private boolean isOnlyAdjustLarger;
     34    private boolean isDynamicAdjustment;
     35    private Map<TableColumn, Integer> columnSizes = new HashMap<TableColumn, Integer>();
     36
     37    /*
     38     *  Specify the table and use default spacing
     39     */
     40    public TableColumnAdjuster(JTable table)
     41    {
     42        this(table, 6);
     43    }
     44
     45    /*
     46     *  Specify the table and spacing
     47     */
     48    public TableColumnAdjuster(JTable table, int spacing)
     49    {
     50        this.table = table;
     51        this.spacing = spacing;
     52        setColumnHeaderIncluded( true );
     53        setColumnDataIncluded( true );
     54        setOnlyAdjustLarger( false );
     55        setDynamicAdjustment( false );
     56        installActions();
     57    }
     58
     59    /*
     60     *  Adjust the widths of all the columns in the table
     61     */
     62    public void adjustColumns()
     63    {
     64        TableColumnModel tcm = table.getColumnModel();
     65
     66        for (int i = 0; i < tcm.getColumnCount(); i++)
     67        {
     68            adjustColumn(i);
     69        }
     70    }
     71
     72    /*
     73     *  Adjust the width of the specified column in the table
     74     */
     75    public void adjustColumn(final int column)
     76    {
     77        TableColumn tableColumn = table.getColumnModel().getColumn(column);
     78
     79        if (! tableColumn.getResizable()) return;
     80
     81        int columnHeaderWidth = getColumnHeaderWidth( column );
     82        int columnDataWidth   = getColumnDataWidth( column );
     83        int preferredWidth  = Math.max(columnHeaderWidth, columnDataWidth);
     84
     85        updateTableColumn(column, preferredWidth);
     86    }
     87
     88    /*
     89     *  Calculated the width based on the column name
     90     */
     91    private int getColumnHeaderWidth(int column)
     92    {
     93        if (! isColumnHeaderIncluded) return 0;
     94
     95        TableColumn tableColumn = table.getColumnModel().getColumn(column);
     96        Object value = tableColumn.getHeaderValue();
     97        TableCellRenderer renderer = tableColumn.getHeaderRenderer();
     98
     99        if (renderer == null)
     100        {
     101            renderer = table.getTableHeader().getDefaultRenderer();
     102        }
     103
     104        Component c = renderer.getTableCellRendererComponent(table, value, false, false, -1, column);
     105        return c.getPreferredSize().width;
     106    }
     107
     108    /*
     109     *  Calculate the width based on the widest cell renderer for the
     110     *  given column.
     111     */
     112    private int getColumnDataWidth(int column)
     113    {
     114        if (! isColumnDataIncluded) return 0;
     115
     116        int preferredWidth = 0;
     117        int maxWidth = table.getColumnModel().getColumn(column).getMaxWidth();
     118
     119        for (int row = 0; row < table.getRowCount(); row++)
     120        {
     121            preferredWidth = Math.max(preferredWidth, getCellDataWidth(row, column));
     122
     123            //  We've exceeded the maximum width, no need to check other rows
     124
     125            if (preferredWidth >= maxWidth)
     126                break;
     127        }
     128
     129        return preferredWidth;
     130    }
     131
     132    /*
     133     *  Get the preferred width for the specified cell
     134     */
     135    private int getCellDataWidth(int row, int column)
     136    {
     137        //  Inovke the renderer for the cell to calculate the preferred width
     138
     139        TableCellRenderer cellRenderer = table.getCellRenderer(row, column);
     140        Component c = table.prepareRenderer(cellRenderer, row, column);
     141        int width = c.getPreferredSize().width + table.getIntercellSpacing().width;
     142
     143        return width;
     144    }
     145
     146    /*
     147     *  Update the TableColumn with the newly calculated width
     148     */
     149    private void updateTableColumn(int column, int width)
     150    {
     151        final TableColumn tableColumn = table.getColumnModel().getColumn(column);
     152
     153        if (! tableColumn.getResizable()) return;
     154
     155        width += spacing;
     156
     157        //  Don't shrink the column width
     158
     159        if (isOnlyAdjustLarger)
     160        {
     161            width = Math.max(width, tableColumn.getPreferredWidth());
     162        }
     163
     164        columnSizes.put(tableColumn, tableColumn.getWidth());
     165
     166        table.getTableHeader().setResizingColumn(tableColumn);
     167        tableColumn.setWidth(width);
     168    }
     169
     170    /*
     171     *  Restore the widths of the columns in the table to its previous width
     172     */
     173    public void restoreColumns()
     174    {
     175        TableColumnModel tcm = table.getColumnModel();
     176
     177        for (int i = 0; i < tcm.getColumnCount(); i++)
     178        {
     179            restoreColumn(i);
     180        }
     181    }
     182
     183    /*
     184     *  Restore the width of the specified column to its previous width
     185     */
     186    private void restoreColumn(int column)
     187    {
     188        TableColumn tableColumn = table.getColumnModel().getColumn(column);
     189        Integer width = columnSizes.get(tableColumn);
     190
     191        if (width != null)
     192        {
     193            table.getTableHeader().setResizingColumn(tableColumn);
     194            tableColumn.setWidth( width.intValue() );
     195        }
     196    }
     197
     198    /*
     199     *  Indicates whether to include the header in the width calculation
     200     */
     201    public void setColumnHeaderIncluded(boolean isColumnHeaderIncluded)
     202    {
     203        this.isColumnHeaderIncluded = isColumnHeaderIncluded;
     204    }
     205
     206    /*
     207     *  Indicates whether to include the model data in the width calculation
     208     */
     209    public void setColumnDataIncluded(boolean isColumnDataIncluded)
     210    {
     211        this.isColumnDataIncluded = isColumnDataIncluded;
     212    }
     213
     214    /*
     215     *  Indicates whether columns can only be increased in size
     216     */
     217    public void setOnlyAdjustLarger(boolean isOnlyAdjustLarger)
     218    {
     219        this.isOnlyAdjustLarger = isOnlyAdjustLarger;
     220    }
     221
     222    /*
     223     *  Indicate whether changes to the model should cause the width to be
     224     *  dynamically recalculated.
     225     */
     226    public void setDynamicAdjustment(boolean isDynamicAdjustment)
     227    {
     228        //  May need to add or remove the TableModelListener when changed
     229
     230        if (this.isDynamicAdjustment != isDynamicAdjustment)
     231        {
     232            if (isDynamicAdjustment)
     233            {
     234                table.addPropertyChangeListener( this );
     235                table.getModel().addTableModelListener( this );
     236            }
     237            else
     238            {
     239                table.removePropertyChangeListener( this );
     240                table.getModel().removeTableModelListener( this );
     241            }
     242        }
     243
     244        this.isDynamicAdjustment = isDynamicAdjustment;
     245    }
     246//
     247//  Implement the PropertyChangeListener
     248//
     249    @Override
     250    public void propertyChange(PropertyChangeEvent e)
     251    {
     252        //  When the TableModel changes we need to update the listeners
     253        //  and column widths
     254
     255        if ("model".equals(e.getPropertyName()))
     256        {
     257            TableModel model = (TableModel)e.getOldValue();
     258            model.removeTableModelListener( this );
     259
     260            model = (TableModel)e.getNewValue();
     261            model.addTableModelListener( this );
     262            adjustColumns();
     263        }
     264    }
     265//
     266//  Implement the TableModelListener
     267//
     268    @Override
     269    public void tableChanged(TableModelEvent e)
     270    {
     271        if (! isColumnDataIncluded) return;
     272
     273        //  Needed when table is sorted.
     274
     275        SwingUtilities.invokeLater(new Runnable()
     276        {
     277            @Override
     278            public void run()
     279            {
     280                //  A cell has been updated
     281
     282                int column = table.convertColumnIndexToView(e.getColumn());
     283
     284                if (e.getType() == TableModelEvent.UPDATE && column != -1)
     285                {
     286                    //  Only need to worry about an increase in width for this cell
     287
     288                    if (isOnlyAdjustLarger)
     289                    {
     290                        int row = e.getFirstRow();
     291                        TableColumn tableColumn = table.getColumnModel().getColumn(column);
     292
     293                        if (tableColumn.getResizable())
     294                        {
     295                            int width = getCellDataWidth(row, column);
     296                            updateTableColumn(column, width);
     297                        }
     298                    }
     299
     300                    //  Could be an increase of decrease so check all rows
     301
     302                    else
     303                    {
     304                        adjustColumn( column );
     305                    }
     306                }
     307
     308                //  The update affected more than one column so adjust all columns
     309
     310                else
     311                {
     312                    adjustColumns();
     313                }
     314            }
     315        });
     316    }
     317
     318    /*
     319     *  Install Actions to give user control of certain functionality.
     320     */
     321    private void installActions()
     322    {
     323        installColumnAction(true,  true,  "adjustColumn",   "control ADD");
     324        installColumnAction(false, true,  "adjustColumns",  "control shift ADD");
     325        installColumnAction(true,  false, "restoreColumn",  "control SUBTRACT");
     326        installColumnAction(false, false, "restoreColumns", "control shift SUBTRACT");
     327
     328        installToggleAction(true,  false, "toggleDynamic",  "control MULTIPLY");
     329        installToggleAction(false, true,  "toggleLarger",   "control DIVIDE");
     330    }
     331
     332    /*
     333     *  Update the input and action maps with a new ColumnAction
     334     */
     335    private void installColumnAction(
     336        boolean isSelectedColumn, boolean isAdjust, String key, String keyStroke)
     337    {
     338        Action action = new ColumnAction(isSelectedColumn, isAdjust);
     339        KeyStroke ks = KeyStroke.getKeyStroke( keyStroke );
     340        table.getInputMap().put(ks, key);
     341        table.getActionMap().put(key, action);
     342    }
     343
     344    /*
     345     *  Update the input and action maps with new ToggleAction
     346     */
     347    private void installToggleAction(
     348        boolean isToggleDynamic, boolean isToggleLarger, String key, String keyStroke)
     349    {
     350        Action action = new ToggleAction(isToggleDynamic, isToggleLarger);
     351        KeyStroke ks = KeyStroke.getKeyStroke( keyStroke );
     352        table.getInputMap().put(ks, key);
     353        table.getActionMap().put(key, action);
     354    }
     355
     356    /*
     357     *  Action to adjust or restore the width of a single column or all columns
     358     */
     359    class ColumnAction extends AbstractAction
     360    {
     361        private boolean isSelectedColumn;
     362        private boolean isAdjust;
     363
     364        public ColumnAction(boolean isSelectedColumn, boolean isAdjust)
     365        {
     366            this.isSelectedColumn = isSelectedColumn;
     367            this.isAdjust = isAdjust;
     368        }
     369
     370        @Override
     371        public void actionPerformed(ActionEvent e)
     372        {
     373            //  Handle selected column(s) width change actions
     374
     375            if (isSelectedColumn)
     376            {
     377                int[] columns = table.getSelectedColumns();
     378
     379                for (int i = 0; i < columns.length; i++)
     380                {
     381                    if (isAdjust)
     382                        adjustColumn(columns[i]);
     383                    else
     384                        restoreColumn(columns[i]);
     385                }
     386            }
     387            else
     388            {
     389                if (isAdjust)
     390                    adjustColumns();
     391                else
     392                    restoreColumns();
     393            }
     394        }
     395    }
     396
     397    /*
     398     *  Toggle properties of the TableColumnAdjuster so the user can
     399     *  customize the functionality to their preferences
     400     */
     401    class ToggleAction extends AbstractAction
     402    {
     403        private boolean isToggleDynamic;
     404        private boolean isToggleLarger;
     405
     406        public ToggleAction(boolean isToggleDynamic, boolean isToggleLarger)
     407        {
     408            this.isToggleDynamic = isToggleDynamic;
     409            this.isToggleLarger = isToggleLarger;
     410        }
     411
     412        @Override
     413        public void actionPerformed(ActionEvent e)
     414        {
     415            if (isToggleDynamic)
     416            {
     417                setDynamicAdjustment(! isDynamicAdjustment);
     418                return;
     419            }
     420
     421            if (isToggleLarger)
     422            {
     423                setOnlyAdjustLarger(! isOnlyAdjustLarger);
     424                return;
     425            }
     426        }
     427    }
     428}
     429 No newline at end of file
  • src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java b/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
    index a37710e35..f01da8bf5 100644
    a b import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListen  
    9393import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
    9494import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
    9595import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
     96import org.openstreetmap.josm.gui.TableColumnAdjuster;
    9697import org.openstreetmap.josm.gui.util.HighlightHelper;
    9798import org.openstreetmap.josm.gui.widgets.CompileSearchTextDecorator;
    9899import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField;
    implements SelectionChangedListener, ActiveLayerChangeListener, DataSetListenerA  
    175176     */
    176177    private final transient TagEditHelper editHelper = new TagEditHelper(tagTable, tagData, valueCount);
    177178
     179    /**
     180     * This sub-object is responsible for resizing columns so that all available space goes to last column
     181    **/
     182    private final TableColumnAdjuster tableColumnAdjuster = new TableColumnAdjuster(tagTable);
     183
    178184    private final transient DataSetListenerAdapter dataChangedAdapter = new DataSetListenerAdapter(this);
    179185    private final HelpAction helpAction = new HelpAction();
    180186    private final TaginfoAction taginfoAction = new TaginfoAction();
    implements SelectionChangedListener, ActiveLayerChangeListener, DataSetListenerA  
    303309
    304310        tagTable.getColumnModel().getColumn(0).setCellRenderer(cellRenderer);
    305311        tagTable.getColumnModel().getColumn(1).setCellRenderer(cellRenderer);
     312
     313        // tableColumnAdjuster is responsible of resizing columns
     314        tagTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
     315
    306316        tagTable.setRowSorter(tagRowSorter);
    307317
    308318        final RemoveHiddenSelection removeHiddenSelection = new RemoveHiddenSelection();
    implements SelectionChangedListener, ActiveLayerChangeListener, DataSetListenerA  
    604614            tags.put(e.getKey(), e.getValue().size() == 1
    605615                    ? e.getValue().keySet().iterator().next() : tr("<different>"));
    606616        }
     617        // readjust columns size on data change
     618        tableColumnAdjuster.adjustColumns();
    607619
    608620        membershipData.setRowCount(0);
    609621