Merge pull request #2389 from SiboVG/issue-2357

[#2357] Remember column width, order, and visibility in preset table
This commit is contained in:
Sibo Van Gool 2023-10-30 11:34:38 +01:00 committed by GitHub
commit b776739464
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 192 additions and 56 deletions

View File

@ -52,6 +52,7 @@ import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import net.sf.openrocket.arch.SystemInfo;
import net.sf.openrocket.gui.util.UITheme;
import net.sf.openrocket.gui.widgets.SaveFileChooser;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import org.slf4j.Logger;
@ -1162,7 +1163,7 @@ public class GeneralOptimizationDialog extends JDialog {
// TODO: update this dynamically instead of hard-coded values
// The macOS file chooser has an issue where it does not update its size when the accessory is added.
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) {
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS && UITheme.isLightTheme(GUIUtil.getUITheme())) {
Dimension currentSize = chooser.getPreferredSize();
Dimension newSize = new Dimension((int) (1.5 * currentSize.width), (int) (1.3 * currentSize.height));
chooser.setPreferredSize(newSize);

View File

@ -36,6 +36,7 @@ import net.sf.openrocket.gui.adaptors.PresetModel;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.gui.util.TableUIPreferences;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.TypedKey;
@ -52,6 +53,7 @@ import net.sf.openrocket.utils.TableRowTraversalPolicy;
*/
@SuppressWarnings("serial")
public class ComponentPresetChooserDialog extends JDialog {
private static final String TABLE_ID = "CmpPrst.";
private static final Translator trans = Application.getTranslator();
@ -154,10 +156,10 @@ public class ComponentPresetChooserDialog extends JDialog {
// need to create componentSelectionTable before filter checkboxes,
// but add to panel after
componentSelectionTable = new ComponentPresetTable(presetType, presets, displayedColumnKeys);
GUIUtil.setAutomaticColumnTableWidths(componentSelectionTable, 20);
// Make the first column (the favorite column) as small as possible
int w = componentSelectionTable.getRowHeight() + 4;
XTableColumnModel tm = componentSelectionTable.getXColumnModel();
//TableColumn tc = componentSelectionTable.getColumnModel().getColumn(0);
TableColumn tc = tm.getColumn(0);
tc.setPreferredWidth(w);
tc.setMaxWidth(w);
@ -166,7 +168,14 @@ public class ComponentPresetChooserDialog extends JDialog {
// The normal left/right and tab/shift-tab key action traverses each cell/column of the table instead of going to the next row.
TableRowTraversalPolicy.setTableRowTraversalPolicy(componentSelectionTable);
// Add checkboxes (legacy, match fore diameter, ...)
panel.add(getFilterCheckboxes(tm, legacyColumnIndex), "wrap para");
// Load the table UI settings from the preferences
boolean legacySelected = showLegacyCheckBox.isSelected();
TableUIPreferences.loadTableUIPreferences(componentSelectionTable, TABLE_ID + component.getComponentName(),
preferences.getTablePreferences());
showLegacyCheckBox.setSelected(legacySelected); // Restore legacy state (may change during UI preference loading)
JScrollPane scrollpane = new JScrollPane();
scrollpane.setViewportView(componentSelectionTable);
@ -202,6 +211,8 @@ public class ComponentPresetChooserDialog extends JDialog {
closeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TableUIPreferences.storeTableUIPreferences(componentSelectionTable, TABLE_ID + component.getComponentName(),
preferences.getTablePreferences());
ComponentPresetChooserDialog.this.setVisible(false);
applySelectedPreset();
}
@ -214,7 +225,6 @@ public class ComponentPresetChooserDialog extends JDialog {
GUIUtil.rememberWindowSize(this);
this.setLocationByPlatform(true);
GUIUtil.rememberWindowPosition(this);
GUIUtil.rememberTableColumnWidths(componentSelectionTable, "Presets" + component.getClass().getCanonicalName());
updateFilters();
}

View File

@ -166,7 +166,7 @@ public class ComponentPresetTable extends JTable {
this.sorter.toggleSortOrder(2); // Sort by the first column (manufacturer) by default
for (TableColumn hiddenColumn : this.hiddenColumns) {
this.tableColumnModel.setColumnVisible(hiddenColumn, false);
this.tableColumnModel.removeColumn(hiddenColumn);
}
JTableHeader header = this.getTableHeader();

View File

@ -239,4 +239,13 @@ public class XTableColumnModel extends DefaultTableColumnModel {
public TableColumn getColumn(int columnIndex, boolean onlyVisible) {
return tableColumns.elementAt(columnIndex);
}
/**
* Returns an Enumeration of all columns, regardless of their visibility.
*
* @return an Enumeration of all TableColumn objects in this model
*/
public Enumeration<TableColumn> getAllColumns() {
return allTableColumns.elements();
}
}

View File

@ -44,7 +44,9 @@ import javax.swing.table.DefaultTableCellRenderer;
import net.sf.openrocket.arch.SystemInfo;
import net.sf.openrocket.gui.components.CsvOptionPanel;
import net.sf.openrocket.gui.util.FileHelper;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.gui.util.UITheme;
import net.sf.openrocket.gui.widgets.SaveFileChooser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -534,7 +536,7 @@ public class SimulationPanel extends JPanel {
// TODO: update this dynamically instead of hard-coded values
// The macOS file chooser has an issue where it does not update its size when the accessory is added.
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) {
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS && UITheme.isLightTheme(GUIUtil.getUITheme())) {
Dimension currentSize = chooser.getPreferredSize();
Dimension newSize = new Dimension((int) (1.5 * currentSize.width), (int) (1.3 * currentSize.height));
chooser.setPreferredSize(newSize);

View File

@ -58,6 +58,7 @@ import javax.swing.event.ChangeListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
@ -405,36 +406,6 @@ public class GUIUtil {
}
}
public static void rememberTableColumnWidths(final JTable table, String keyName) {
final String key = keyName == null ? table.getClass().getName() : keyName;
Enumeration<TableColumn> columns = table.getColumnModel().getColumns();
while (columns.hasMoreElements()) {
TableColumn column = columns.nextElement();
column.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("width")) {
log.debug("Storing width of " + table.getName() + "-" + column + ": " + column.getWidth());
((SwingPreferences) Application.getPreferences()).setTableColumnWidth(
key, column.getModelIndex(), column.getWidth());
}
}
});
final Integer width = ((SwingPreferences) Application.getPreferences()).getTableColumnWidth(
key, column.getModelIndex());
if (width != null) {
column.setPreferredWidth(width);
} else {
column.setPreferredWidth(getOptimalColumnWidth(table, column.getModelIndex()));
}
}
}
public static void rememberTableColumnWidths(final JTable table) {
rememberTableColumnWidths(table, null);
}
public static int getOptimalColumnWidth(JTable table, int columnIndex) {
if (columnIndex >= table.getColumnModel().getColumnCount()) {
return -1;
@ -477,26 +448,46 @@ public class GUIUtil {
window.setLocation(position);
}
}
public static void setAutomaticColumnTableWidths(JTable table, int max) {
/**
* Computes the optimal column widths for the specified table, based on the content in all rows for that column,
* and optionally also the column header content.
* @param table the table
* @param max the maximum width for a column
* @param includeHeaderWidth whether to include the width of the column header
* @return an array of column widths
*/
public static int[] computeOptimalColumnWidths(JTable table, int max, boolean includeHeaderWidth) {
int columns = table.getColumnCount();
int widths[] = new int[columns];
int[] widths = new int[columns];
Arrays.fill(widths, 1);
// Consider the width required by the header text if includeHeaderWidth is true
if (includeHeaderWidth) {
TableCellRenderer headerRenderer = table.getTableHeader().getDefaultRenderer();
for (int col = 0; col < columns; col++) {
TableColumn column = table.getColumnModel().getColumn(col);
Component headerComp = headerRenderer.getTableCellRendererComponent(table, column.getHeaderValue(), false, false, 0, col);
widths[col] = headerComp.getPreferredSize().width;
}
}
// Compare header width to the width required by the cell data
for (int row = 0; row < table.getRowCount(); row++) {
for (int col = 0; col < columns; col++) {
Object value = table.getValueAt(row, col);
//System.out.println("row=" + row + " col=" + col + " : " + value);
widths[col] = Math.max(widths[col], value == null ? 0 : value.toString().length());
TableCellRenderer cellRenderer = table.getCellRenderer(row, col);
Component cellComp = cellRenderer.getTableCellRendererComponent(table, value, false, false, row, col);
int cellWidth = cellComp.getPreferredSize().width;
widths[col] = Math.max(widths[col], cellWidth);
}
}
for (int col = 0; col < columns; col++) {
//System.err.println("Setting column " + col + " to width " + widths[col]);
table.getColumnModel().getColumn(col).setPreferredWidth(Math.min(widths[col], max) * 100);
widths[col] = Math.min(widths[col], max * 100); // Adjusting for your max value and scaling
}
return widths;
}
public static Window getWindowAncestor(Component c) {

View File

@ -55,7 +55,7 @@ public abstract class PreferencesExporter {
// TODO: update this dynamically instead of hard-coded values
// The macOS file chooser has an issue where it does not update its size when the accessory is added.
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) {
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS && UITheme.isLightTheme(GUIUtil.getUITheme())) {
Dimension currentSize = chooser.getPreferredSize();
Dimension newSize = new Dimension((int) (1.35 * currentSize.width), (int) (1.2 * currentSize.height));
chooser.setPreferredSize(newSize);

View File

@ -136,6 +136,22 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public Preferences getPreferences() {
return PREFNODE;
}
/**
* Returns the preference node responsible for saving UI window information (position, size...)
* @return the preference node for window information
*/
public Preferences getWindowsPreferences() {
return PREFNODE.node(NODE_WINDOWS);
}
/**
* Returns the preference node responsible for saving table information (column widths, order...)
* @return the preference node for table information
*/
public Preferences getTablePreferences() {
return PREFNODE.node(NODE_TABLES);
}
public void clearPreferences() {
try {
@ -550,7 +566,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public Point getWindowPosition(Class<?> c) {
int x, y;
String pref = PREFNODE.node(NODE_WINDOWS).get("position." + c.getCanonicalName(), null);
String pref = getWindowsPreferences().get("position." + c.getCanonicalName(), null);
if (pref == null)
return null;
@ -575,7 +591,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
}
public void setWindowPosition(Class<?> c, Point p) {
PREFNODE.node(NODE_WINDOWS).put("position." + c.getCanonicalName(), "" + p.x + "," + p.y);
getWindowsPreferences().put("position." + c.getCanonicalName(), "" + p.x + "," + p.y);
storeVersion();
}
@ -603,7 +619,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public Dimension getWindowSize(Class<?> c) {
int x, y;
String pref = PREFNODE.node(NODE_WINDOWS).get("size." + c.getCanonicalName(), null);
String pref = getWindowsPreferences().get("size." + c.getCanonicalName(), null);
if (pref == null)
return null;
@ -622,22 +638,22 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public boolean isWindowMaximized(Class<?> c) {
String pref = PREFNODE.node(NODE_WINDOWS).get("size." + c.getCanonicalName(), null);
String pref = getWindowsPreferences().get("size." + c.getCanonicalName(), null);
return "max".equals(pref);
}
public void setWindowSize(Class<?> c, Dimension d) {
PREFNODE.node(NODE_WINDOWS).put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
getWindowsPreferences().put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
storeVersion();
}
public void setWindowMaximized(Class<?> c) {
PREFNODE.node(NODE_WINDOWS).put("size." + c.getCanonicalName(), "max");
getWindowsPreferences().put("size." + c.getCanonicalName(), "max");
storeVersion();
}
public Integer getTableColumnWidth(String keyName, int columnIdx) {
String pref = PREFNODE.node(NODE_TABLES).get(
String pref = getTablePreferences().get(
"cw." + keyName + "." + columnIdx, null);
if (pref == null)
return null;
@ -655,7 +671,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
}
public void setTableColumnWidth(String keyName, int columnIdx, Integer width) {
PREFNODE.node(NODE_TABLES).put(
getTablePreferences().put(
"cw." + keyName + "." + columnIdx, width.toString());
storeVersion();
}

View File

@ -0,0 +1,107 @@
package net.sf.openrocket.gui.util;
import net.sf.openrocket.gui.dialogs.preset.XTableColumnModel;
import javax.swing.JTable;
import javax.swing.table.TableColumn;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.prefs.Preferences;
/**
* Utility class for storing and loading the following table UI preferences:
* - column width
* - column order
* - column visibility
*/
public class TableUIPreferences {
private static final String TABLE_COLUMN_WIDTH_PREFIX = ".cw.";
private static final String TABLE_COLUMN_ORDER_PREFIX = ".co.";
private static final String TABLE_COLUMN_VISIBILITY_PREFIX = ".cv.";
public static void storeTableUIPreferences(JTable table, String tableName, Preferences preferences) {
// Store column widths
for (int i = 0; i < table.getColumnCount(); i++) {
TableColumn column = table.getColumnModel().getColumn(i);
int width = column.getWidth();
preferences.putInt(tableName + TABLE_COLUMN_WIDTH_PREFIX + column.getIdentifier(), width);
}
// Store column order
for (int i = 0; i < table.getColumnCount(); i++) {
TableColumn column = table.getColumnModel().getColumn(i);
preferences.putInt(tableName + TABLE_COLUMN_ORDER_PREFIX + column.getIdentifier(), i);
}
// Store column visibility
if (table.getColumnModel() instanceof XTableColumnModel customModel) {
Enumeration<TableColumn> columns = customModel.getAllColumns();
while (columns.hasMoreElements()) {
TableColumn column = columns.nextElement();
boolean isVisible = customModel.isColumnVisible(column);
preferences.putBoolean(tableName + TABLE_COLUMN_VISIBILITY_PREFIX + column.getIdentifier(), isVisible);
}
}
}
public static void loadTableUIPreferences(JTable table, String tableName, Preferences preferences) {
// Ensure all columns are visible
Enumeration<TableColumn> allColumns = table.getColumnModel().getColumns();
List<TableColumn> removedColumns = new ArrayList<>();
while (allColumns.hasMoreElements()) {
TableColumn column = allColumns.nextElement();
if (table.convertColumnIndexToView(column.getModelIndex()) == -1) {
removedColumns.add(column);
}
}
for (TableColumn col : removedColumns) {
table.addColumn(col);
}
// Get all columns from the table's column model and restore visibility
if (table.getColumnModel() instanceof XTableColumnModel customModel) {
Enumeration<TableColumn> columns = customModel.getAllColumns(); // Use getAllColumns to get all columns, including invisible ones
while (columns.hasMoreElements()) {
TableColumn column = columns.nextElement();
String identifier = column.getIdentifier().toString();
// Default to true if the preference is not found
boolean isVisible = preferences.getBoolean(tableName + TABLE_COLUMN_VISIBILITY_PREFIX + identifier, true);
customModel.setColumnVisible(column, isVisible);
}
}
// Now, restore column order
for (int i = 0; i < table.getColumnCount(); i++) {
TableColumn column = table.getColumnModel().getColumn(i);
int storedOrder = preferences.getInt(tableName + TABLE_COLUMN_ORDER_PREFIX + column.getIdentifier(), i);
if (storedOrder != i && storedOrder < table.getColumnCount()) {
table.moveColumn(table.convertColumnIndexToView(column.getModelIndex()), storedOrder);
}
}
// Check if any column width is missing from preferences
boolean computeOptimalWidths = false;
for (int i = 0; i < table.getColumnCount() && !computeOptimalWidths; i++) {
TableColumn column = table.getColumnModel().getColumn(i);
if (preferences.get(tableName + TABLE_COLUMN_WIDTH_PREFIX + column.getIdentifier(), null) == null) {
computeOptimalWidths = true;
}
}
// If any column width is missing, compute optimal widths for all columns
int[] optimalWidths = null;
if (computeOptimalWidths) {
optimalWidths = GUIUtil.computeOptimalColumnWidths(table, 20, true);
}
// Restore column widths
for (int i = 0; i < table.getColumnCount(); i++) {
TableColumn column = table.getColumnModel().getColumn(i);
int defaultWidth = (optimalWidths != null) ? optimalWidths[i] : column.getWidth();
int width = preferences.getInt(tableName + TABLE_COLUMN_WIDTH_PREFIX + column.getIdentifier(), defaultWidth);
column.setPreferredWidth(width);
}
}
}