From 0cefc0946a22d09e20a42a57297c178962ae5c1c Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sun, 30 Jul 2017 14:05:23 +0200 Subject: [PATCH] Added table column adjuster class to help in formatting songs table. --- .../handiebot/utils/TableColumnAdjuster.java | 429 ++++++++++++++++++ src/main/java/handiebot/view/BotWindow.java | 22 +- .../view/PlaylistSelectionListener.java | 6 +- 3 files changed, 435 insertions(+), 22 deletions(-) create mode 100644 src/main/java/handiebot/utils/TableColumnAdjuster.java diff --git a/src/main/java/handiebot/utils/TableColumnAdjuster.java b/src/main/java/handiebot/utils/TableColumnAdjuster.java new file mode 100644 index 0000000..96cb5b3 --- /dev/null +++ b/src/main/java/handiebot/utils/TableColumnAdjuster.java @@ -0,0 +1,429 @@ +package handiebot.utils; + +import javax.swing.*; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import javax.swing.table.TableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashMap; +import java.util.Map; + +/* + * Class to manage the widths of colunmns in a table. + * + * Various properties control how the width of the column is calculated. + * Another property controls whether column width calculation should be dynamic. + * Finally, various Actions will be added to the table to allow the user + * to customize the functionality. + * + * This class was designed to be used with tables that use an auto resize mode + * of AUTO_RESIZE_OFF. With all other modes you are constrained as the width + * of the columns must fit inside the table. So if you increase one column, one + * or more of the other columns must decrease. Because of this the resize mode + * of RESIZE_ALL_COLUMNS will work the best. + */ +public class TableColumnAdjuster implements PropertyChangeListener, TableModelListener +{ + private JTable table; + private int spacing; + private boolean isColumnHeaderIncluded; + private boolean isColumnDataIncluded; + private boolean isOnlyAdjustLarger; + private boolean isDynamicAdjustment; + private Map columnSizes = new HashMap(); + + /* + * Specify the table and use default spacing + */ + public TableColumnAdjuster(JTable table) + { + this(table, 6); + } + + /* + * Specify the table and spacing + */ + public TableColumnAdjuster(JTable table, int spacing) + { + this.table = table; + this.spacing = spacing; + setColumnHeaderIncluded( true ); + setColumnDataIncluded( true ); + setOnlyAdjustLarger( false ); + setDynamicAdjustment( false ); + installActions(); + } + + /* + * Adjust the widths of all the columns in the table + */ + public void adjustColumns() + { + TableColumnModel tcm = table.getColumnModel(); + + for (int i = 0; i < tcm.getColumnCount(); i++) + { + adjustColumn(i); + } + } + + /* + * Adjust the width of the specified column in the table + */ + public void adjustColumn(final int column) + { + TableColumn tableColumn = table.getColumnModel().getColumn(column); + + if (! tableColumn.getResizable()) return; + + int columnHeaderWidth = getColumnHeaderWidth( column ); + int columnDataWidth = getColumnDataWidth( column ); + int preferredWidth = Math.max(columnHeaderWidth, columnDataWidth); + + updateTableColumn(column, preferredWidth); + } + + /* + * Calculated the width based on the column name + */ + private int getColumnHeaderWidth(int column) + { + if (! isColumnHeaderIncluded) return 0; + + TableColumn tableColumn = table.getColumnModel().getColumn(column); + Object value = tableColumn.getHeaderValue(); + TableCellRenderer renderer = tableColumn.getHeaderRenderer(); + + if (renderer == null) + { + renderer = table.getTableHeader().getDefaultRenderer(); + } + + Component c = renderer.getTableCellRendererComponent(table, value, false, false, -1, column); + return c.getPreferredSize().width; + } + + /* + * Calculate the width based on the widest cell renderer for the + * given column. + */ + private int getColumnDataWidth(int column) + { + if (! isColumnDataIncluded) return 0; + + int preferredWidth = 0; + int maxWidth = table.getColumnModel().getColumn(column).getMaxWidth(); + + for (int row = 0; row < table.getRowCount(); row++) + { + preferredWidth = Math.max(preferredWidth, getCellDataWidth(row, column)); + + // We've exceeded the maximum width, no need to check other rows + + if (preferredWidth >= maxWidth) + break; + } + + return preferredWidth; + } + + /* + * Get the preferred width for the specified cell + */ + private int getCellDataWidth(int row, int column) + { + // Inovke the renderer for the cell to calculate the preferred width + + TableCellRenderer cellRenderer = table.getCellRenderer(row, column); + Component c = table.prepareRenderer(cellRenderer, row, column); + int width = c.getPreferredSize().width + table.getIntercellSpacing().width; + + return width; + } + + /* + * Update the TableColumn with the newly calculated width + */ + private void updateTableColumn(int column, int width) + { + final TableColumn tableColumn = table.getColumnModel().getColumn(column); + + if (! tableColumn.getResizable()) return; + + width += spacing; + + // Don't shrink the column width + + if (isOnlyAdjustLarger) + { + width = Math.max(width, tableColumn.getPreferredWidth()); + } + + columnSizes.put(tableColumn, new Integer(tableColumn.getWidth())); + + table.getTableHeader().setResizingColumn(tableColumn); + tableColumn.setWidth(width); + } + + /* + * Restore the widths of the columns in the table to its previous width + */ + public void restoreColumns() + { + TableColumnModel tcm = table.getColumnModel(); + + for (int i = 0; i < tcm.getColumnCount(); i++) + { + restoreColumn(i); + } + } + + /* + * Restore the width of the specified column to its previous width + */ + private void restoreColumn(int column) + { + TableColumn tableColumn = table.getColumnModel().getColumn(column); + Integer width = columnSizes.get(tableColumn); + + if (width != null) + { + table.getTableHeader().setResizingColumn(tableColumn); + tableColumn.setWidth( width.intValue() ); + } + } + + /* + * Indicates whether to include the header in the width calculation + */ + public void setColumnHeaderIncluded(boolean isColumnHeaderIncluded) + { + this.isColumnHeaderIncluded = isColumnHeaderIncluded; + } + + /* + * Indicates whether to include the model data in the width calculation + */ + public void setColumnDataIncluded(boolean isColumnDataIncluded) + { + this.isColumnDataIncluded = isColumnDataIncluded; + } + + /* + * Indicates whether columns can only be increased in size + */ + public void setOnlyAdjustLarger(boolean isOnlyAdjustLarger) + { + this.isOnlyAdjustLarger = isOnlyAdjustLarger; + } + + /* + * Indicate whether changes to the model should cause the width to be + * dynamically recalculated. + */ + public void setDynamicAdjustment(boolean isDynamicAdjustment) + { + // May need to add or remove the TableModelListener when changed + + if (this.isDynamicAdjustment != isDynamicAdjustment) + { + if (isDynamicAdjustment) + { + table.addPropertyChangeListener( this ); + table.getModel().addTableModelListener( this ); + } + else + { + table.removePropertyChangeListener( this ); + table.getModel().removeTableModelListener( this ); + } + } + + this.isDynamicAdjustment = isDynamicAdjustment; + } +// +// Implement the PropertyChangeListener +// + public void propertyChange(PropertyChangeEvent e) + { + // When the TableModel changes we need to update the listeners + // and column widths + + if ("model".equals(e.getPropertyName())) + { + TableModel model = (TableModel)e.getOldValue(); + model.removeTableModelListener( this ); + + model = (TableModel)e.getNewValue(); + model.addTableModelListener( this ); + adjustColumns(); + } + } +// +// Implement the TableModelListener +// + public void tableChanged(TableModelEvent e) + { + if (! isColumnDataIncluded) return; + + // Needed when table is sorted. + + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + // A cell has been updated + + int column = table.convertColumnIndexToView(e.getColumn()); + + if (e.getType() == TableModelEvent.UPDATE && column != -1) + { + // Only need to worry about an increase in width for this cell + + if (isOnlyAdjustLarger) + { + int row = e.getFirstRow(); + TableColumn tableColumn = table.getColumnModel().getColumn(column); + + if (tableColumn.getResizable()) + { + int width = getCellDataWidth(row, column); + updateTableColumn(column, width); + } + } + + // Could be an increase of decrease so check all rows + + else + { + adjustColumn( column ); + } + } + + // The update affected more than one column so adjust all columns + + else + { + adjustColumns(); + } + } + }); + } + + /* + * Install Actions to give user control of certain functionality. + */ + private void installActions() + { + installColumnAction(true, true, "adjustColumn", "control ADD"); + installColumnAction(false, true, "adjustColumns", "control shift ADD"); + installColumnAction(true, false, "restoreColumn", "control SUBTRACT"); + installColumnAction(false, false, "restoreColumns", "control shift SUBTRACT"); + + installToggleAction(true, false, "toggleDynamic", "control MULTIPLY"); + installToggleAction(false, true, "toggleLarger", "control DIVIDE"); + } + + /* + * Update the input and action maps with a new ColumnAction + */ + private void installColumnAction( + boolean isSelectedColumn, boolean isAdjust, String key, String keyStroke) + { + Action action = new ColumnAction(isSelectedColumn, isAdjust); + KeyStroke ks = KeyStroke.getKeyStroke( keyStroke ); + table.getInputMap().put(ks, key); + table.getActionMap().put(key, action); + } + + /* + * Update the input and action maps with new ToggleAction + */ + private void installToggleAction( + boolean isToggleDynamic, boolean isToggleLarger, String key, String keyStroke) + { + Action action = new ToggleAction(isToggleDynamic, isToggleLarger); + KeyStroke ks = KeyStroke.getKeyStroke( keyStroke ); + table.getInputMap().put(ks, key); + table.getActionMap().put(key, action); + } + + /* + * Action to adjust or restore the width of a single column or all columns + */ + class ColumnAction extends AbstractAction + { + private boolean isSelectedColumn; + private boolean isAdjust; + + public ColumnAction(boolean isSelectedColumn, boolean isAdjust) + { + this.isSelectedColumn = isSelectedColumn; + this.isAdjust = isAdjust; + } + + @Override + public void actionPerformed(ActionEvent e) + { + // Handle selected column(s) width change actions + + if (isSelectedColumn) + { + int[] columns = table.getSelectedColumns(); + + for (int i = 0; i < columns.length; i++) + { + if (isAdjust) + adjustColumn(columns[i]); + else + restoreColumn(columns[i]); + } + } + else + { + if (isAdjust) + adjustColumns(); + else + restoreColumns(); + } + } + } + + /* + * Toggle properties of the TableColumnAdjuster so the user can + * customize the functionality to their preferences + */ + class ToggleAction extends AbstractAction + { + private boolean isToggleDynamic; + private boolean isToggleLarger; + + public ToggleAction(boolean isToggleDynamic, boolean isToggleLarger) + { + this.isToggleDynamic = isToggleDynamic; + this.isToggleLarger = isToggleLarger; + } + + @Override + public void actionPerformed(ActionEvent e) + { + if (isToggleDynamic) + { + setDynamicAdjustment(! isDynamicAdjustment); + return; + } + + if (isToggleLarger) + { + setOnlyAdjustLarger(! isOnlyAdjustLarger); + return; + } + } + } +} diff --git a/src/main/java/handiebot/view/BotWindow.java b/src/main/java/handiebot/view/BotWindow.java index 3eaa321..fa8ca9a 100644 --- a/src/main/java/handiebot/view/BotWindow.java +++ b/src/main/java/handiebot/view/BotWindow.java @@ -7,8 +7,6 @@ import handiebot.view.tableModels.SongsTableModel; import javax.imageio.ImageIO; import javax.swing.*; import javax.swing.event.ListSelectionListener; -import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumnModel; import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -57,6 +55,7 @@ public class BotWindow extends JFrame { //Playlist name scroll pane. playlistTable.setRowSelectionAllowed(true); playlistTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + playlistTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); playlistTable.getSelectionModel().addListSelectionListener(new PlaylistSelectionListener(this.songsTableModel, playlistTable, songsTable)); JScrollPane playlistNamesScrollPane = new JScrollPane(playlistTable); playlistNamesScrollPane.setPreferredSize(new Dimension(250, 200)); @@ -121,25 +120,6 @@ public class BotWindow extends JFrame { this.playlistDisplayPanel.setVisible(!this.playlistDisplayPanel.isVisible()); } - /** - * Automatically resizes a table to shrink the index column. - * @param table The table to resize. - */ - public static void autoSizeTable(JTable table){ - final TableColumnModel columnModel = table.getColumnModel(); - int freeSpace = 230; - for (int col = 0; col < columnModel.getColumnCount(); col++) { - int width = 0; - for (int row = 0; row < table.getRowCount(); row++) { - TableCellRenderer renderer = table.getCellRenderer(row, col); - Component component = table.prepareRenderer(renderer, row, col); - width = Math.max(component.getPreferredSize().width + 1, width); - } - columnModel.getColumn(col).setPreferredWidth(width); - freeSpace -= width; - } - } - public JTextPane getOutputArea(){ return this.outputArea; } diff --git a/src/main/java/handiebot/view/PlaylistSelectionListener.java b/src/main/java/handiebot/view/PlaylistSelectionListener.java index a5fd9c5..7e32144 100644 --- a/src/main/java/handiebot/view/PlaylistSelectionListener.java +++ b/src/main/java/handiebot/view/PlaylistSelectionListener.java @@ -1,6 +1,7 @@ package handiebot.view; import handiebot.lavaplayer.playlist.Playlist; +import handiebot.utils.TableColumnAdjuster; import handiebot.view.tableModels.SongsTableModel; import javax.swing.*; @@ -18,19 +19,21 @@ public class PlaylistSelectionListener implements ListSelectionListener { //The table that shows the playlist names. private JTable table; + //The table for the songs. private JTable songsTable; + private TableColumnAdjuster tca; public PlaylistSelectionListener(SongsTableModel songsModel, JTable table, JTable songsTable){ this.songsModel = songsModel; this.table = table; this.songsTable = songsTable; + this.tca = new TableColumnAdjuster(songsTable); } @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()){ updatePlaylistData(); - BotWindow.autoSizeTable(songsTable); } } @@ -44,6 +47,7 @@ public class PlaylistSelectionListener implements ListSelectionListener { Playlist playlist = new Playlist(selectedValue); playlist.load(); this.songsModel.setPlaylist(playlist); + this.tca.adjustColumns(); } }