From a1a5ccc61f21b177d1cba975dbf3f96d943e1103 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sat, 12 Dec 2020 23:33:57 +0100 Subject: [PATCH 01/25] Implement better scaling of main table showing entries Co-authored-by: Dominik Voigt --- CHANGELOG.md | 3 +- .../org/jabref/gui/maintable/MainTable.java | 6 +- .../gui/maintable/MainTableColumnFactory.java | 14 ++- .../gui/maintable/MainTableColumnModel.java | 3 + .../SmartConstrainedResizePolicy.java | 87 +++++-------------- 5 files changed, 41 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfbb06fed9a..921fa8cbc53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,8 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We changed connect timeouts for server requests to 30 seconds in general and 5 seconds for GROBID server (special) and improved user notifications on connection issues. [7026](https://github.com/JabRef/jabref/pull/7026) - We changed the order of the library tab context menu items. [#7171](https://github.com/JabRef/jabref/issues/7171) - We changed the way linked files are opened on Linux to use the native openFile method, compatible with confined packages. [7037](https://github.com/JabRef/jabref/pull/7037) -- We refined the entry preview to show the full names of authors and editors, to list the editor only if no author is present, have the year ealier. [#7083](https://github.com/JabRef/jabref/issues/7083) +- We refined the entry preview to show the full names of authors and editors, to list the editor only if no author is present, have the year earlier. [#7083](https://github.com/JabRef/jabref/issues/7083) +- We changed the resize behavior of table columns to change the width of the current column only. [#967](https://github.com/JabRef/jabref/issues/967) ### Fixed diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index d0e42e0221c..1a4bbe78cca 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -137,16 +137,14 @@ public MainTable(MainTableDataModel model, } } */ - mainTablePreferences.getColumnPreferences().getColumnSortOrder().forEach(columnModel -> + mainTablePreferences.getColumnPreferences().getColumnSortOrder().forEach(columnModel -> this.getColumns().stream() .map(column -> (MainTableColumn) column) .filter(column -> column.getModel().equals(columnModel)) .findFirst() .ifPresent(column -> this.getSortOrder().add(column))); - if (mainTablePreferences.getResizeColumnsToFit()) { - this.setColumnResizePolicy(new SmartConstrainedResizePolicy()); - } + this.setColumnResizePolicy(new SmartConstrainedResizePolicy()); this.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index 8ff3dd7efc9..8b437d54c7c 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -37,6 +37,7 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.SpecialField; +import org.jabref.model.entry.field.StandardField; import org.jabref.model.groups.AbstractGroup; import org.jabref.model.util.OptionalUtil; import org.jabref.preferences.PreferencesService; @@ -109,7 +110,16 @@ public MainTableColumnFactory(BibDatabaseContext database, default: case NORMALFIELD: if (!column.getQualifier().isBlank()) { - columns.add(createFieldColumn(column)); + TableColumn fieldColumn = createFieldColumn(column); + columns.add(fieldColumn); + if (column.getQualifier().equalsIgnoreCase(StandardField.YEAR.getName())) { + // We adjust the min width and the current width, but not the max width to allow the user to enlarge this field + // 75 is chosen, because of the optimal width of a four digit field + fieldColumn.setMinWidth(60); + fieldColumn.setPrefWidth(60); + } else { + fieldColumn.setMinWidth(ColumnPreferences.DEFAULT_COLUMN_WIDTH); + } } break; } @@ -125,7 +135,7 @@ public static void setExactWidth(TableColumn column, double width) { } /** - * Creates a column with a continous number + * Creates a column with a continuous number */ private TableColumn createIndexColumn(MainTableColumnModel columnModel) { TableColumn column = new MainTableColumn<>(columnModel); diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java index 0edf1dbde17..f7975c3b155 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java @@ -124,6 +124,9 @@ public Type getType() { return typeProperty.getValue(); } + /** + * Returns the field name of the column (e.g., year) + */ public String getQualifier() { return qualifierProperty.getValue(); } diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index e6c543fb02f..a275076e67f 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -6,6 +6,7 @@ import java.util.List; import javafx.scene.control.ResizeFeaturesBase; +import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumnBase; import javafx.scene.control.TableView; import javafx.util.Callback; @@ -25,80 +26,36 @@ public class SmartConstrainedResizePolicy implements Callback table) { - double tableWidth = getContentWidth(table); - List> visibleLeafColumns = table.getVisibleLeafColumns(); - double totalWidth = visibleLeafColumns.stream().mapToDouble(TableColumnBase::getWidth).sum(); - - if (Math.abs(totalWidth - tableWidth) > 1) { - double totalPrefWidth = visibleLeafColumns.stream().mapToDouble(TableColumnBase::getPrefWidth).sum(); - if (totalPrefWidth > 0) { - for (TableColumnBase col : visibleLeafColumns) { - double share = col.getPrefWidth() / totalPrefWidth; - double newSize = tableWidth * share; - - // Just to make sure that we are staying under the total table width (due to rounding errors) - newSize -= 2; - - resize(col, newSize - col.getWidth()); - } - } - } - - return false; - } - - private void resize(TableColumnBase column, double delta) { - // We have to use reflection since TableUtil is not visible to us - try { - // TODO: reflective access, should be removed - Class clazz = Class.forName("javafx.scene.control.TableUtil"); - Method constrainedResize = clazz.getDeclaredMethod("resize", TableColumnBase.class, double.class); - constrainedResize.setAccessible(true); - constrainedResize.invoke(null, column, delta); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { - LOGGER.error("Could not invoke resize in TableUtil", e); - } - } - private Boolean constrainedResize(TableView.ResizeFeatures prop) { TableView table = prop.getTable(); List> visibleLeafColumns = table.getVisibleLeafColumns(); - return constrainedResize(prop, - false, - getContentWidth(table) - 2, - visibleLeafColumns); - } - - private Boolean constrainedResize(TableView.ResizeFeatures prop, Boolean isFirstRun, Double contentWidth, List> visibleLeafColumns) { - // We have to use reflection since TableUtil is not visible to us - try { - // TODO: reflective access, should be removed - Class clazz = Class.forName("javafx.scene.control.TableUtil"); - Method constrainedResize = clazz.getDeclaredMethod("constrainedResize", ResizeFeaturesBase.class, Boolean.TYPE, Double.TYPE, List.class); - constrainedResize.setAccessible(true); - Object returnValue = constrainedResize.invoke(null, prop, isFirstRun, contentWidth, visibleLeafColumns); - return (Boolean) returnValue; - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { - LOGGER.error("Could not invoke constrainedResize in TableUtil", e); - return false; + Double delta = prop.getDelta(); + TableColumn userChosenColumnToResize = prop.getColumn(); + + double oldWidth = userChosenColumnToResize.getWidth(); + double newWidth; + if (delta < 0) { + double minWidth = userChosenColumnToResize.getMinWidth(); + LOGGER.trace("MinWidth {}", minWidth); + newWidth = Math.max(minWidth, oldWidth + delta); + } else { + double maxWidth = userChosenColumnToResize.getMaxWidth(); + LOGGER.trace("MaxWidth {}", maxWidth); + newWidth = Math.min(maxWidth, oldWidth + delta); } - } - - private Double getContentWidth(TableView table) { - try { - // TODO: reflective access, should be removed - Field privateStringField = TableView.class.getDeclaredField("contentWidth"); - privateStringField.setAccessible(true); - return (Double) privateStringField.get(table); - } catch (IllegalAccessException | NoSuchFieldException e) { - return 0d; + LOGGER.trace("Size: {} -> {}", oldWidth, newWidth); + if (oldWidth == newWidth) { + return false; } + userChosenColumnToResize.setPrefWidth(newWidth); + return true; } } From e91bb8dba0e2460ae82a1b3a4a5edfa7935ac622 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 14 Dec 2020 20:32:01 +0100 Subject: [PATCH 02/25] Fix number --- .../java/org/jabref/gui/maintable/MainTableColumnFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index 8b437d54c7c..4595bf11b51 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -114,7 +114,7 @@ public MainTableColumnFactory(BibDatabaseContext database, columns.add(fieldColumn); if (column.getQualifier().equalsIgnoreCase(StandardField.YEAR.getName())) { // We adjust the min width and the current width, but not the max width to allow the user to enlarge this field - // 75 is chosen, because of the optimal width of a four digit field + // 60 is chosen, because of the optimal width of a four digit field fieldColumn.setMinWidth(60); fieldColumn.setPrefWidth(60); } else { From 8f522df09ad1b3018c48310d93ea09d4b43543ee Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 14 Dec 2020 21:19:45 +0100 Subject: [PATCH 03/25] Remove resizeColumnsToFit - Remove preferences - Remove MainTablePreferences (and migrate left-overs into ColumnPreferences) - Fix name of "Show dedicated file columns" (instead of "Show extra columns") Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Co-authored-by: Dominik Voigt --- CHANGELOG.md | 4 +-- docs/adr/0016-mutable-preferences-objects.md | 4 +-- .../gui/maintable/ColumnPreferences.java | 11 +++++++- .../org/jabref/gui/maintable/MainTable.java | 6 ++-- .../gui/maintable/MainTablePreferences.java | 25 ----------------- .../PersistenceVisualStateTable.java | 3 +- .../org/jabref/gui/preferences/TableTab.fxml | 4 +-- .../jabref/gui/preferences/TableTabView.java | 6 ++-- .../gui/preferences/TableTabViewModel.java | 28 ++++++------------- .../jabref/preferences/JabRefPreferences.java | 27 ++++++------------ .../preferences/PreferencesService.java | 5 ---- src/main/resources/l10n/JabRef_en.properties | 3 +- 12 files changed, 42 insertions(+), 84 deletions(-) delete mode 100644 src/main/java/org/jabref/gui/maintable/MainTablePreferences.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 921fa8cbc53..b518ad4a83d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,3 @@ - # Changelog All notable changes to this project will be documented in this file. @@ -47,9 +46,10 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We changed the way JabRef displays the title of a tab and of the window. [4161](https://github.com/JabRef/jabref/issues/4161) - We changed connect timeouts for server requests to 30 seconds in general and 5 seconds for GROBID server (special) and improved user notifications on connection issues. [7026](https://github.com/JabRef/jabref/pull/7026) - We changed the order of the library tab context menu items. [#7171](https://github.com/JabRef/jabref/issues/7171) +- We changed the resize behavior of table columns to change the width of the current column only. [#967](https://github.com/JabRef/jabref/issues/967) +- We renamed "Show extra columns" to "Show dedicated file columns". [#7181](https://github.com/JabRef/jabref/pull/7181) - We changed the way linked files are opened on Linux to use the native openFile method, compatible with confined packages. [7037](https://github.com/JabRef/jabref/pull/7037) - We refined the entry preview to show the full names of authors and editors, to list the editor only if no author is present, have the year earlier. [#7083](https://github.com/JabRef/jabref/issues/7083) -- We changed the resize behavior of table columns to change the width of the current column only. [#967](https://github.com/JabRef/jabref/issues/967) ### Fixed diff --git a/docs/adr/0016-mutable-preferences-objects.md b/docs/adr/0016-mutable-preferences-objects.md index 96fadaf5e6f..f49511fea0d 100644 --- a/docs/adr/0016-mutable-preferences-objects.md +++ b/docs/adr/0016-mutable-preferences-objects.md @@ -6,8 +6,8 @@ To create an immutable preferences object every time seems to be a waste of time ## Considered Options -* Create a new object every time a preferences object should be altered by a with*-method, similar to a builder. -* Alter the existing object and return it. +* Alter the existing object and return it (by a `with*` -method, similar to a builder, but changing the object at hand). +* Create a new object every time a preferences object should be altered ## Decision Outcome diff --git a/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java b/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java index d79b3a6857b..c5762f2ae87 100644 --- a/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java +++ b/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java @@ -8,11 +8,15 @@ public class ColumnPreferences { public static final double ICON_COLUMN_WIDTH = 16 + 12; // add some additional space to improve appearance private final List columns; + private final List columnSortOrder; - public ColumnPreferences(List columns, List columnSortOrder) { + private final boolean dedicatedFileColumnsEnabled; + + public ColumnPreferences(List columns, List columnSortOrder, boolean dedicatedFileColumnsEnabled) { this.columns = columns; this.columnSortOrder = columnSortOrder; + this.dedicatedFileColumnsEnabled = dedicatedFileColumnsEnabled; } public List getColumns() { @@ -22,4 +26,9 @@ public List getColumns() { public List getColumnSortOrder() { return columnSortOrder; } + + public boolean isDedicatedFileColumnsEnabled() { + return dedicatedFileColumnsEnabled; + } + } diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index 1a4bbe78cca..dc1f560add2 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -83,7 +83,7 @@ public MainTable(MainTableDataModel model, this.database = Objects.requireNonNull(database); this.model = model; UndoManager undoManager = libraryTab.getUndoManager(); - MainTablePreferences mainTablePreferences = preferencesService.getMainTablePreferences(); + ColumnPreferences columnPreferences = preferencesService.getColumnPreferences(); importHandler = new ImportHandler( dialogService, database, externalFileTypes, @@ -137,7 +137,7 @@ public MainTable(MainTableDataModel model, } } */ - mainTablePreferences.getColumnPreferences().getColumnSortOrder().forEach(columnModel -> + columnPreferences.getColumnSortOrder().forEach(columnModel -> this.getColumns().stream() .map(column -> (MainTableColumn) column) .filter(column -> column.getModel().equals(columnModel)) @@ -330,7 +330,7 @@ private void handleOnDragDetected(TableRow row, BibEntry List entries = getSelectionModel().getSelectedItems().stream().map(BibEntryTableViewModel::getEntry).collect(Collectors.toList()); - // The following is necesary to initiate the drag and drop in javafx, although we don't need the contents + // The following is necessary to initiate the drag and drop in javafx, although we don't need the contents // It doesn't work without ClipboardContent content = new ClipboardContent(); Dragboard dragboard = startDragAndDrop(TransferMode.MOVE); diff --git a/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java b/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java deleted file mode 100644 index 7698893b9dd..00000000000 --- a/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.jabref.gui.maintable; - -public class MainTablePreferences { - private final ColumnPreferences columnPreferences; - private final boolean resizeColumnsToFit; - private final boolean extraFileColumnsEnabled; - - public MainTablePreferences(ColumnPreferences columnPreferences, boolean resizeColumnsToFit, boolean extraFileColumnsEnabled) { - this.columnPreferences = columnPreferences; - this.resizeColumnsToFit = resizeColumnsToFit; - this.extraFileColumnsEnabled = extraFileColumnsEnabled; - } - - public ColumnPreferences getColumnPreferences() { - return columnPreferences; - } - - public boolean getResizeColumnsToFit() { - return resizeColumnsToFit; - } - - public boolean getExtraFileColumnsEnabled() { - return extraFileColumnsEnabled; - } -} diff --git a/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java b/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java index 6b695be291d..53ad33db32a 100644 --- a/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java +++ b/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java @@ -40,7 +40,8 @@ private void updateColumnPreferences() { .collect(Collectors.toList()), mainTable.getSortOrder().stream() .map(column -> ((MainTableColumn) column).getModel()) - .collect(Collectors.toList()) + .collect(Collectors.toList()), + preferences.getColumnPreferences().isDedicatedFileColumnsEnabled() )); } } diff --git a/src/main/java/org/jabref/gui/preferences/TableTab.fxml b/src/main/java/org/jabref/gui/preferences/TableTab.fxml index d04196d0831..0b5cfe97ad2 100644 --- a/src/main/java/org/jabref/gui/preferences/TableTab.fxml +++ b/src/main/java/org/jabref/gui/preferences/TableTab.fxml @@ -98,8 +98,8 @@ text="%Write values of special fields as separate fields to BibTeX" toggleGroup="$specialFieldsStoreMode"/> - - + diff --git a/src/main/java/org/jabref/gui/preferences/TableTabView.java b/src/main/java/org/jabref/gui/preferences/TableTabView.java index 309ae7da5dd..7d31b1949bc 100644 --- a/src/main/java/org/jabref/gui/preferences/TableTabView.java +++ b/src/main/java/org/jabref/gui/preferences/TableTabView.java @@ -36,8 +36,7 @@ public class TableTabView extends AbstractPreferenceTabView i @FXML private Button specialFieldsHelp; @FXML private RadioButton specialFieldsSyncKeywords; @FXML private RadioButton specialFieldsSerialize; - @FXML private CheckBox extraFileColumnsEnable; - @FXML private CheckBox autoResizeColumns; + @FXML private CheckBox dedicatedFileColumnsEnable; @FXML private RadioButton namesNatbib; @FXML private RadioButton nameAsIs; @@ -121,8 +120,7 @@ private void setupBindings() { specialFieldsEnable.selectedProperty().bindBidirectional(viewModel.specialFieldsEnabledProperty()); specialFieldsSyncKeywords.selectedProperty().bindBidirectional(viewModel.specialFieldsSyncKeywordsProperty()); specialFieldsSerialize.selectedProperty().bindBidirectional(viewModel.specialFieldsSerializeProperty()); - extraFileColumnsEnable.selectedProperty().bindBidirectional(viewModel.extraFileColumnsEnabledProperty()); - autoResizeColumns.selectedProperty().bindBidirectional(viewModel.autoResizeColumnsProperty()); + dedicatedFileColumnsEnable.selectedProperty().bindBidirectional(viewModel.extraFileColumnsEnabledProperty()); namesNatbib.selectedProperty().bindBidirectional(viewModel.namesNatbibProperty()); nameAsIs.selectedProperty().bindBidirectional(viewModel.nameAsIsProperty()); diff --git a/src/main/java/org/jabref/gui/preferences/TableTabViewModel.java b/src/main/java/org/jabref/gui/preferences/TableTabViewModel.java index a2f464b4b2b..02577905547 100644 --- a/src/main/java/org/jabref/gui/preferences/TableTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/TableTabViewModel.java @@ -20,7 +20,6 @@ import org.jabref.gui.maintable.ColumnPreferences; import org.jabref.gui.maintable.MainTableColumnModel; import org.jabref.gui.maintable.MainTableNameFormatPreferences; -import org.jabref.gui.maintable.MainTablePreferences; import org.jabref.gui.specialfields.SpecialFieldsPreferences; import org.jabref.gui.util.NoSelectionModel; import org.jabref.logic.l10n.Localization; @@ -63,8 +62,7 @@ public MainTableColumnModel fromString(String string) { private final BooleanProperty specialFieldsEnabledProperty = new SimpleBooleanProperty(); private final BooleanProperty specialFieldsSyncKeywordsProperty = new SimpleBooleanProperty(); private final BooleanProperty specialFieldsSerializeProperty = new SimpleBooleanProperty(); - private final BooleanProperty extraFileColumnsEnabledProperty = new SimpleBooleanProperty(); - private final BooleanProperty autoResizeColumnsProperty = new SimpleBooleanProperty(); + private final BooleanProperty dedicatedFileColumnsEnabledProperty = new SimpleBooleanProperty(); private final BooleanProperty namesNatbibProperty = new SimpleBooleanProperty(); private final BooleanProperty nameAsIsProperty = new SimpleBooleanProperty(); @@ -96,7 +94,7 @@ public TableTabViewModel(DialogService dialogService, PreferencesService prefere } }); - extraFileColumnsEnabledProperty.addListener((observable, oldValue, newValue) -> { + dedicatedFileColumnsEnabledProperty.addListener((observable, oldValue, newValue) -> { if (newValue) { insertExtraFileColumns(); } else { @@ -115,16 +113,14 @@ public TableTabViewModel(DialogService dialogService, PreferencesService prefere @Override public void setValues() { - MainTablePreferences initialMainTablePreferences = preferences.getMainTablePreferences(); - initialColumnPreferences = initialMainTablePreferences.getColumnPreferences(); + initialColumnPreferences = preferences.getColumnPreferences(); initialSpecialFieldsPreferences = preferences.getSpecialFieldsPreferences(); MainTableNameFormatPreferences initialNameFormatPreferences = preferences.getMainTableNameFormatPreferences(); specialFieldsEnabledProperty.setValue(initialSpecialFieldsPreferences.isSpecialFieldsEnabled()); specialFieldsSyncKeywordsProperty.setValue(initialSpecialFieldsPreferences.shouldAutoSyncSpecialFieldsToKeyWords()); specialFieldsSerializeProperty.setValue(initialSpecialFieldsPreferences.shouldSerializeSpecialFields()); - extraFileColumnsEnabledProperty.setValue(initialMainTablePreferences.getExtraFileColumnsEnabled()); - autoResizeColumnsProperty.setValue(initialMainTablePreferences.getResizeColumnsToFit()); + dedicatedFileColumnsEnabledProperty.setValue(initialColumnPreferences.isDedicatedFileColumnsEnabled()); fillColumnList(); @@ -150,7 +146,7 @@ public void setValues() { insertSpecialFieldColumns(); } - if (extraFileColumnsEnabledProperty.getValue()) { + if (dedicatedFileColumnsEnabledProperty.getValue()) { insertExtraFileColumns(); } @@ -240,13 +236,11 @@ public void moveColumnDown() { @Override public void storeSettings() { - MainTablePreferences newMainTablePreferences = preferences.getMainTablePreferences(); - preferences.storeMainTablePreferences(new MainTablePreferences( + preferences.storeColumnPreferences( new ColumnPreferences( columnsListProperty.getValue(), - newMainTablePreferences.getColumnPreferences().getColumnSortOrder()), - autoResizeColumnsProperty.getValue(), - extraFileColumnsEnabledProperty.getValue() + initialColumnPreferences.getColumnSortOrder(), + dedicatedFileColumnsEnabledProperty.getValue() )); SpecialFieldsPreferences newSpecialFieldsPreferences = new SpecialFieldsPreferences( @@ -332,11 +326,7 @@ public BooleanProperty specialFieldsSerializeProperty() { } public BooleanProperty extraFileColumnsEnabledProperty() { - return this.extraFileColumnsEnabledProperty; - } - - public BooleanProperty autoResizeColumnsProperty() { - return autoResizeColumnsProperty; + return this.dedicatedFileColumnsEnabledProperty; } public BooleanProperty namesNatbibProperty() { diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 2d6904939da..9ca51953a0c 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -49,7 +49,6 @@ import org.jabref.gui.maintable.MainTableNameFormatPreferences; import org.jabref.gui.maintable.MainTableNameFormatPreferences.AbbreviationStyle; import org.jabref.gui.maintable.MainTableNameFormatPreferences.DisplayStyle; -import org.jabref.gui.maintable.MainTablePreferences; import org.jabref.gui.mergeentries.MergeEntries; import org.jabref.gui.search.SearchDisplayMode; import org.jabref.gui.specialfields.SpecialFieldsPreferences; @@ -177,7 +176,10 @@ public class JabRefPreferences implements PreferencesService { public static final String KEYWORD_SEPARATOR = "groupKeywordSeparator"; public static final String AUTO_ASSIGN_GROUP = "autoAssignGroup"; public static final String DISPLAY_GROUP_COUNT = "displayGroupCount"; - public static final String EXTRA_FILE_COLUMNS = "extraFileColumns"; + + // different content of the constant because of backwards compatibility + public static final String DEDICATED_FILE_COLUMNS = "extraFileColumns"; + public static final String OVERRIDE_DEFAULT_FONT_SIZE = "overrideDefaultFontSize"; public static final String MAIN_FONT_SIZE = "mainFontSize"; @@ -537,7 +539,7 @@ private JabRefPreferences() { defaults.put(SHOW_ADVANCED_HINTS, Boolean.TRUE); defaults.put(RENAME_ON_MOVE_FILE_TO_FILE_DIR, Boolean.TRUE); - defaults.put(EXTRA_FILE_COLUMNS, Boolean.FALSE); + defaults.put(DEDICATED_FILE_COLUMNS, Boolean.FALSE); defaults.put(PROTECTED_TERMS_ENABLED_INTERNAL, convertListToString(ProtectedTermsLoader.getInternalLists())); defaults.put(PROTECTED_TERMS_DISABLED_INTERNAL, ""); @@ -2026,7 +2028,8 @@ private void updateColumnSortOrder() { public ColumnPreferences getColumnPreferences() { return new ColumnPreferences( createMainTableColumns(), - createMainTableColumnSortOrder()); + createMainTableColumnSortOrder(), + getBoolean(DEDICATED_FILE_COLUMNS, false)); } /** @@ -2053,24 +2056,12 @@ public void storeColumnPreferences(ColumnPreferences columnPreferences) { .map(MainTableColumnModel::getName) .collect(Collectors.toList())); + putBoolean(DEDICATED_FILE_COLUMNS, columnPreferences.isDedicatedFileColumnsEnabled()); + // Update cache mainTableColumns = columnPreferences.getColumns(); } - @Override - public MainTablePreferences getMainTablePreferences() { - return new MainTablePreferences(getColumnPreferences(), - getBoolean(AUTO_RESIZE_MODE), - getBoolean(EXTRA_FILE_COLUMNS)); - } - - @Override - public void storeMainTablePreferences(MainTablePreferences mainTablePreferences) { - storeColumnPreferences(mainTablePreferences.getColumnPreferences()); - putBoolean(AUTO_RESIZE_MODE, mainTablePreferences.getResizeColumnsToFit()); - putBoolean(EXTRA_FILE_COLUMNS, mainTablePreferences.getExtraFileColumnsEnabled()); - } - @Override public MainTableNameFormatPreferences getMainTableNameFormatPreferences() { DisplayStyle displayStyle = diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index b8151f6fda1..0407e6e51d6 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -13,7 +13,6 @@ import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.maintable.ColumnPreferences; import org.jabref.gui.maintable.MainTableNameFormatPreferences; -import org.jabref.gui.maintable.MainTablePreferences; import org.jabref.gui.specialfields.SpecialFieldsPreferences; import org.jabref.gui.util.Theme; import org.jabref.logic.bibtex.FieldContentFormatterPreferences; @@ -228,10 +227,6 @@ public interface PreferencesService { void storeColumnPreferences(ColumnPreferences columnPreferences); - MainTablePreferences getMainTablePreferences(); - - void storeMainTablePreferences(MainTablePreferences mainTablePreferences); - MainTableNameFormatPreferences getMainTableNameFormatPreferences(); void storeMainTableNameFormatPreferences(MainTableNameFormatPreferences preferences); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 607637ce4f2..a4d68c1fe6b 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -348,7 +348,7 @@ Finished\ writing\ XMP\ for\ %0\ file\ (%1\ skipped,\ %2\ errors).=Finished writ First\ select\ the\ entries\ you\ want\ keys\ to\ be\ generated\ for.=First select the entries you want keys to be generated for. -Fit\ table\ horizontally\ on\ screen=Fit table horizontally on screen +Show\ dedicated\ file\ columns=Show dedicated file columns Float=Float Format\:\ Tab\:field;field;...\ (e.g.\ General\:url;pdf;note...)=Format\: Tab\:field;field;... (e.g. General\:url;pdf;note...) @@ -1219,7 +1219,6 @@ Newline\ separator=Newline separator Save\ in\ current\ table\ sort\ order=Save in current table sort order Save\ entries\ ordered\ as\ specified=Save entries ordered as specified -Show\ extra\ columns=Show extra columns Parsing\ error=Parsing error illegal\ backslash\ expression=illegal backslash expression From 1acd12e80416d7202113b7ad5d324c11105fec30 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 14 Dec 2020 22:53:00 +0100 Subject: [PATCH 04/25] Fix checkstyle --- .../jabref/gui/maintable/SmartConstrainedResizePolicy.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index a275076e67f..829b909069e 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -1,11 +1,7 @@ package org.jabref.gui.maintable; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.List; -import javafx.scene.control.ResizeFeaturesBase; import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumnBase; import javafx.scene.control.TableView; From f5e2d9fb42f1bff3bbf3283df4b22aa880a0cbab Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Thu, 17 Dec 2020 23:46:23 +0100 Subject: [PATCH 05/25] Add smart resize if content fits into table width Co-authored-by: Dominik Voigt --- CHANGELOG.md | 2 +- .../gui/maintable/MainTableColumnFactory.java | 12 +- .../SmartConstrainedResizePolicy.java | 155 ++++++++++++++++-- 3 files changed, 153 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60f5234ff60..c9cd6e6ca94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We changed the way JabRef displays the title of a tab and of the window. [4161](https://github.com/JabRef/jabref/issues/4161) - We changed connect timeouts for server requests to 30 seconds in general and 5 seconds for GROBID server (special) and improved user notifications on connection issues. [7026](https://github.com/JabRef/jabref/pull/7026) - We changed the order of the library tab context menu items. [#7171](https://github.com/JabRef/jabref/issues/7171) -- We changed the resize behavior of table columns to change the width of the current column only. [#967](https://github.com/JabRef/jabref/issues/967) +- We changed the resize behavior of table columns to have smart fit into the table if there is enough space. [#967](https://github.com/JabRef/jabref/issues/967) - We renamed "Show extra columns" to "Show dedicated file columns". [#7181](https://github.com/JabRef/jabref/pull/7181) - We changed the way linked files are opened on Linux to use the native openFile method, compatible with confined packages. [7037](https://github.com/JabRef/jabref/pull/7037) - We refined the entry preview to show the full names of authors and editors, to list the editor only if no author is present, have the year earlier. [#7083](https://github.com/JabRef/jabref/issues/7083) diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index 4595bf11b51..d484cd1eee4 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -36,6 +36,7 @@ import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.InternalField; import org.jabref.model.entry.field.SpecialField; import org.jabref.model.entry.field.StandardField; import org.jabref.model.groups.AbstractGroup; @@ -113,10 +114,15 @@ public MainTableColumnFactory(BibDatabaseContext database, TableColumn fieldColumn = createFieldColumn(column); columns.add(fieldColumn); if (column.getQualifier().equalsIgnoreCase(StandardField.YEAR.getName())) { - // We adjust the min width and the current width, but not the max width to allow the user to enlarge this field // 60 is chosen, because of the optimal width of a four digit field - fieldColumn.setMinWidth(60); - fieldColumn.setPrefWidth(60); + setExactWidth(fieldColumn, 60); + } else if (column.getQualifier().equalsIgnoreCase(InternalField.TYPE_HEADER.getName())) { + // 80 is chosen, because of the optimal width of the entry type + setExactWidth(fieldColumn, 90); + } else if (column.getQualifier().equalsIgnoreCase(StandardField.KEY.getName())) { + // The citation key is smaller than the other fields + // The other option is to set a max width. However, we think that some users have loooooong keys, they want to see + fieldColumn.setMinWidth(ColumnPreferences.DEFAULT_COLUMN_WIDTH / 2); } else { fieldColumn.setMinWidth(ColumnPreferences.DEFAULT_COLUMN_WIDTH); } diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 829b909069e..1e2ec117b4d 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -1,6 +1,10 @@ package org.jabref.gui.maintable; +import java.lang.reflect.Field; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumnBase; @@ -19,39 +23,166 @@ public class SmartConstrainedResizePolicy implements Callback expansionShare = new HashMap<>(); + @Override public Boolean call(TableView.ResizeFeatures prop) { if (prop.getColumn() == null) { - // table is initialized - // no need to adjust - return false; + // happens at initialization and at window resize + LOGGER.debug("Table is fully rendered"); + // This way, the proportions of the columns are kept during resize of the window + determineWidthShare(prop.getTable()); + return rearrangeColumns(prop.getTable()); } else { return constrainedResize(prop); } } - private Boolean constrainedResize(TableView.ResizeFeatures prop) { - TableView table = prop.getTable(); + /** + * Determines the share of the total width each column has + */ + private void determineWidthShare(TableView table) { + // We need to store the initial preferred width, because "setWidth()" does not exist + // There is only "setMinWidth", "setMaxWidth", and "setPrefWidth + + Double totalInitialPreferredWidths; List> visibleLeafColumns = table.getVisibleLeafColumns(); - Double delta = prop.getDelta(); - TableColumn userChosenColumnToResize = prop.getColumn(); + totalInitialPreferredWidths = visibleLeafColumns.stream() + .filter(TableColumnBase::isResizable) + .mapToDouble(TableColumnBase::getPrefWidth).sum(); + for (TableColumnBase column : visibleLeafColumns) { + if (column.isResizable()) { + expansionShare.put(column, column.getPrefWidth() / totalInitialPreferredWidths); + } else { + expansionShare.put(column, 0d); + } + } + } - double oldWidth = userChosenColumnToResize.getWidth(); + /** + * Determines the new width of the column based on the requested delta. It respects the min and max width of the column. + * + * @param column The column the resize is requested + * @param delta The delta requested + * @return the new size, Optional.empty() if no resize is possible + */ + private Optional determineNewWidth(TableColumn column, Double delta) { + // This is com.sun.javafx.scene.control.skin.Utils.boundedSize with more comments and Optionals + + // Calculate newWidth based on delta and constraint of the column + double oldWidth = column.getWidth(); double newWidth; if (delta < 0) { - double minWidth = userChosenColumnToResize.getMinWidth(); + double minWidth = column.getMinWidth(); LOGGER.trace("MinWidth {}", minWidth); newWidth = Math.max(minWidth, oldWidth + delta); } else { - double maxWidth = userChosenColumnToResize.getMaxWidth(); + double maxWidth = column.getMaxWidth(); LOGGER.trace("MaxWidth {}", maxWidth); newWidth = Math.min(maxWidth, oldWidth + delta); } LOGGER.trace("Size: {} -> {}", oldWidth, newWidth); if (oldWidth == newWidth) { - return false; + return Optional.empty(); + } + return Optional.of(newWidth); + } + + /** + * Resizes the table based on the content. The main driver is that if the content might fit into the table without horizontal scroll bar. + * In case the content fitted before the resize and will fit afterwards, the delta is distributed among the remaining columns - instead of just moving the columns right of the current column. + * In case the content does not fit anymore, a horizontal scroll bar is shown. + * In all cases the minimum/maximum width of each column is respected. + */ + private Boolean constrainedResize(TableView.ResizeFeatures prop) { + TableView table = prop.getTable(); + Double tableWidth = getContentWidth(table); + List> visibleLeafColumns = table.getVisibleLeafColumns(); + Double requiredWidth = visibleLeafColumns.stream().mapToDouble(TableColumnBase::getWidth).sum(); + Double delta = prop.getDelta(); + + TableColumn userChosenColumnToResize = prop.getColumn(); + + boolean columnsCanFitTable = requiredWidth + delta < tableWidth; + if (columnsCanFitTable) { + LOGGER.debug("User shrunk column in that way thus that window can contain all the columns"); + if (requiredWidth >= tableWidth) { + LOGGER.debug("Before, the content did not fit. Rearrange everything."); + return rearrangeColumns(table); + } + LOGGER.debug("Everything already fit. We distribute the delta now."); + + // Content does already fit + // We "just" need to readjust the column widths + determineNewWidth(userChosenColumnToResize, delta) + .ifPresent(newWidth -> { + double currentWidth = userChosenColumnToResize.getWidth(); + Double deltaToApply = newWidth - currentWidth; + for (TableColumnBase col : visibleLeafColumns) { + Double share = expansionShare.get(col); + Double newSize; + if (col.equals(prop.getColumn())) { + // The column resized by the user gets the delta; + newSize = newWidth; + } else { + // The columns not explicitly resized by the user get the negative delta + newSize = col.getWidth() - share * deltaToApply; + } + newSize = Math.min(newSize, col.getMaxWidth()); + col.setPrefWidth(newSize); + } + }); + return true; + } + + LOGGER.debug("Window smaller than content"); + + determineNewWidth(userChosenColumnToResize, delta).ifPresent(newWidth -> + userChosenColumnToResize.setPrefWidth(newWidth)); + return true; + } + + /** + * Rearranges the widths of all columns according to their shares (proportional to their preferred widths) + */ + private Boolean rearrangeColumns(TableView table) { + Double tableWidth = getContentWidth(table); + List> visibleLeafColumns = table.getVisibleLeafColumns(); + + Double remainingAvailableWidth = tableWidth - getSumMinWidthOfColumns(table); + + for (TableColumnBase col : visibleLeafColumns) { + double share = expansionShare.get(col); + double newSize = col.getMinWidth() + share * remainingAvailableWidth; + + // Just to make sure that we are staying under the total table width (due to rounding errors) + newSize -= 2; + + newSize = Math.min(newSize, col.getMaxWidth()); + + col.setPrefWidth(newSize); } - userChosenColumnToResize.setPrefWidth(newWidth); return true; } + + /** + * Sums up the minimum widths of each column in the table + */ + private Double getSumMinWidthOfColumns(TableView table) { + return table.getColumns().stream().mapToDouble(TableColumnBase::getMinWidth).sum(); + } + + /** + * Computes and returns the width required by the content of the table + */ + private Double getContentWidth(TableView table) { + try { + // TODO: reflective access, should be removed + Field privateStringField = TableView.class.getDeclaredField("contentWidth"); + privateStringField.setAccessible(true); + return (Double) privateStringField.get(table); + } catch (IllegalAccessException | NoSuchFieldException e) { + return 0d; + } + } } From bcea66cf7f32deb48170ba39847c6bc28bb50862 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sat, 19 Dec 2020 00:17:16 +0100 Subject: [PATCH 06/25] Address some comments Co-authored-by: Dominik Voigt --- .../gui/maintable/ColumnPreferences.java | 1 + .../gui/maintable/MainTableColumnFactory.java | 17 +- .../SmartConstrainedResizePolicy.java | 158 +++++++++++------- 3 files changed, 109 insertions(+), 67 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java b/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java index c5762f2ae87..d56e7eb5ad5 100644 --- a/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java +++ b/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java @@ -4,6 +4,7 @@ public class ColumnPreferences { + public static final double DEFAULT_COLUMN_MIN_WIDTH = 10; public static final double DEFAULT_COLUMN_WIDTH = 100; public static final double ICON_COLUMN_WIDTH = 16 + 12; // add some additional space to improve appearance diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index d484cd1eee4..21d5b4075a8 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -78,7 +78,6 @@ public MainTableColumnFactory(BibDatabaseContext database, List> columns = new ArrayList<>(); columnPreferences.getColumns().forEach(column -> { - switch (column.getType()) { case INDEX: columns.add(createIndexColumn(column)); @@ -113,18 +112,16 @@ public MainTableColumnFactory(BibDatabaseContext database, if (!column.getQualifier().isBlank()) { TableColumn fieldColumn = createFieldColumn(column); columns.add(fieldColumn); + fieldColumn.setPrefWidth(ColumnPreferences.DEFAULT_COLUMN_WIDTH); if (column.getQualifier().equalsIgnoreCase(StandardField.YEAR.getName())) { - // 60 is chosen, because of the optimal width of a four digit field - setExactWidth(fieldColumn, 60); + // 60 is chosen, because of the optimal width of a four digit number + fieldColumn.setPrefWidth(60); } else if (column.getQualifier().equalsIgnoreCase(InternalField.TYPE_HEADER.getName())) { - // 80 is chosen, because of the optimal width of the entry type - setExactWidth(fieldColumn, 90); - } else if (column.getQualifier().equalsIgnoreCase(StandardField.KEY.getName())) { - // The citation key is smaller than the other fields - // The other option is to set a max width. However, we think that some users have loooooong keys, they want to see - fieldColumn.setMinWidth(ColumnPreferences.DEFAULT_COLUMN_WIDTH / 2); + // 90 is chosen, because of the optimal width of the entry type + fieldColumn.setPrefWidth(90); } else { - fieldColumn.setMinWidth(ColumnPreferences.DEFAULT_COLUMN_WIDTH); + fieldColumn.setMinWidth(ColumnPreferences.DEFAULT_COLUMN_MIN_WIDTH); + fieldColumn.setPrefWidth(ColumnPreferences.DEFAULT_COLUMN_WIDTH); } } break; diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 1e2ec117b4d..6e7af7cec1b 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumnBase; @@ -24,49 +25,68 @@ public class SmartConstrainedResizePolicy implements Callback expansionShare = new HashMap<>(); + List> resizableColumns; + private TableColumn lastModifiedColumn = null; @Override public Boolean call(TableView.ResizeFeatures prop) { - if (prop.getColumn() == null) { + TableColumn column = prop.getColumn(); + if (column == null) { // happens at initialization and at window resize LOGGER.debug("Table is fully rendered"); + TableView table = prop.getTable(); + resizableColumns = table.getVisibleLeafColumns().stream() + .filter(TableColumnBase::isResizable) + .collect(Collectors.toList()); // This way, the proportions of the columns are kept during resize of the window - determineWidthShare(prop.getTable()); - return rearrangeColumns(prop.getTable()); + determineExpansionShare(); + + // Rearrange columns if content fits into displayed table + // Otherwise, the default is good enough + if (contentFitsIntoTable(table)) { + return rearrangeColumns(table); + } + return false; } else { + if (!column.equals(lastModifiedColumn)) { + lastModifiedColumn = column; + // This way, the proportions of the columns are kept during resize of the window + determineExpansionShare(); + } return constrainedResize(prop); } } + private boolean contentFitsIntoTable(TableView table) { + Double tableWidth = getContentWidth(table); + List> visibleLeafColumns = table.getVisibleLeafColumns(); + Double currentTableContentWidth = visibleLeafColumns.stream().mapToDouble(TableColumnBase::getWidth).sum(); + return tableWidth >= currentTableContentWidth; + } + /** * Determines the share of the total width each column has */ - private void determineWidthShare(TableView table) { + private void determineExpansionShare() { // We need to store the initial preferred width, because "setWidth()" does not exist // There is only "setMinWidth", "setMaxWidth", and "setPrefWidth - - Double totalInitialPreferredWidths; - List> visibleLeafColumns = table.getVisibleLeafColumns(); - totalInitialPreferredWidths = visibleLeafColumns.stream() - .filter(TableColumnBase::isResizable) - .mapToDouble(TableColumnBase::getPrefWidth).sum(); - for (TableColumnBase column : visibleLeafColumns) { - if (column.isResizable()) { - expansionShare.put(column, column.getPrefWidth() / totalInitialPreferredWidths); - } else { - expansionShare.put(column, 0d); - } + Double allResizableColumnsWidth = resizableColumns.stream().mapToDouble(TableColumnBase::getPrefWidth).sum(); + for (TableColumnBase column : resizableColumns) { + expansionShare.put(column, column.getPrefWidth() / allResizableColumnsWidth); } } /** * Determines the new width of the column based on the requested delta. It respects the min and max width of the column. + *
+ * This method is also called if the table content is wider than the window. Thus, it does not respect the overall table width; + * "just" the width constraints of the given column * * @param column The column the resize is requested * @param delta The delta requested * @return the new size, Optional.empty() if no resize is possible */ - private Optional determineNewWidth(TableColumn column, Double delta) { + private Optional determineNewWidth(TableColumnBase column, Double delta) { // This is com.sun.javafx.scene.control.skin.Utils.boundedSize with more comments and Optionals // Calculate newWidth based on delta and constraint of the column @@ -98,71 +118,95 @@ private Boolean constrainedResize(TableView.ResizeFeatures prop) { TableView table = prop.getTable(); Double tableWidth = getContentWidth(table); List> visibleLeafColumns = table.getVisibleLeafColumns(); - Double requiredWidth = visibleLeafColumns.stream().mapToDouble(TableColumnBase::getWidth).sum(); + // The current able content width contains all visible columns: the resizable ones and the non-resizable ones + Double currentTableContentWidth = visibleLeafColumns.stream().mapToDouble(TableColumnBase::getWidth).sum(); Double delta = prop.getDelta(); TableColumn userChosenColumnToResize = prop.getColumn(); - boolean columnsCanFitTable = requiredWidth + delta < tableWidth; + boolean columnsCanFitTable = currentTableContentWidth + delta < tableWidth; if (columnsCanFitTable) { - LOGGER.debug("User shrunk column in that way thus that window can contain all the columns"); - if (requiredWidth >= tableWidth) { - LOGGER.debug("Before, the content did not fit. Rearrange everything."); + LOGGER.debug("User window size in that way thus that window can contain all the columns"); + if (currentTableContentWidth >= tableWidth) { + LOGGER.debug("Before, the content did not fit. Now it fits. Rearrange everything."); return rearrangeColumns(table); } LOGGER.debug("Everything already fit. We distribute the delta now."); // Content does already fit // We "just" need to readjust the column widths - determineNewWidth(userChosenColumnToResize, delta) - .ifPresent(newWidth -> { - double currentWidth = userChosenColumnToResize.getWidth(); - Double deltaToApply = newWidth - currentWidth; - for (TableColumnBase col : visibleLeafColumns) { - Double share = expansionShare.get(col); - Double newSize; - if (col.equals(prop.getColumn())) { - // The column resized by the user gets the delta; - newSize = newWidth; - } else { - // The columns not explicitly resized by the user get the negative delta - newSize = col.getWidth() - share * deltaToApply; - } - newSize = Math.min(newSize, col.getMaxWidth()); - col.setPrefWidth(newSize); - } - }); - return true; + Optional newWidthOptional = determineNewWidth(userChosenColumnToResize, delta); + newWidthOptional.ifPresent(newWidth -> { + distributeDelta(table, userChosenColumnToResize, newWidth); + }); + return newWidthOptional.isPresent(); } LOGGER.debug("Window smaller than content"); - determineNewWidth(userChosenColumnToResize, delta).ifPresent(newWidth -> - userChosenColumnToResize.setPrefWidth(newWidth)); - return true; + Optional newWidth = determineNewWidth(userChosenColumnToResize, delta); + newWidth.ifPresent(userChosenColumnToResize::setPrefWidth); + return newWidth.isPresent(); + } + + private void distributeDelta(TableView table, TableColumnBase userChosenColumnToResize, Double newWidth) { + userChosenColumnToResize.setPrefWidth(newWidth); + + List> columnsToResize = resizableColumns.stream().filter(col -> !col.equals(userChosenColumnToResize)).collect(Collectors.toList()); + + Double tableWidth = table.getWidth(); + Double newContentWidth; + + do + { + // in case the userChosenColumnToResize got bigger, the remaining available width will get below 0 --> other columns need to be shrunk + Double remainingAvailableWidth = tableWidth - getContentWidth(table); + LOGGER.debug("Distributing delta {}", remainingAvailableWidth); + for (TableColumnBase col : columnsToResize) { + double share = expansionShare.get(col); + determineNewWidth(col, share * remainingAvailableWidth) + // in case we can do something, do it + // otherwise, the next loop iteration will distribute it + .ifPresent(col::setPrefWidth); + } + newContentWidth = getContentWidth(table); + } while (Math.abs(tableWidth - newContentWidth) > 5d); } /** * Rearranges the widths of all columns according to their shares (proportional to their preferred widths) */ private Boolean rearrangeColumns(TableView table) { - Double tableWidth = getContentWidth(table); - List> visibleLeafColumns = table.getVisibleLeafColumns(); - - Double remainingAvailableWidth = tableWidth - getSumMinWidthOfColumns(table); + Double initialContentWidth = getContentWidth(table); - for (TableColumnBase col : visibleLeafColumns) { - double share = expansionShare.get(col); - double newSize = col.getMinWidth() + share * remainingAvailableWidth; + // Implementation idea: + // Each column has to have at least the minimum width + // First, set the minimum width of all columns + // Then, there is available non-assigned space + // Distribute this space in a fair way to all resizable columns - // Just to make sure that we are staying under the total table width (due to rounding errors) - newSize -= 2; + for (TableColumnBase col : resizableColumns) { + col.setPrefWidth(col.getMinWidth()); + } - newSize = Math.min(newSize, col.getMaxWidth()); + Double tableWidth = table.getWidth(); + Double newContentWidth; + do + { + Double remainingAvailableWidth = Math.max(0, tableWidth - getContentWidth(table)); + LOGGER.debug("Distributing delta {}", remainingAvailableWidth); + for (TableColumnBase col : resizableColumns) { + double share = expansionShare.get(col); + // Precondition in our case: col has to have minimum width + determineNewWidth(col, share * remainingAvailableWidth) + // in case we can do something, do it + // otherwise, the next loop iteration will distribute it + .ifPresent(col::setPrefWidth); + } + newContentWidth = getContentWidth(table); + } while (Math.abs(tableWidth - newContentWidth) > 5d); - col.setPrefWidth(newSize); - } - return true; + return (Math.floor(initialContentWidth - newContentWidth) != 0d); } /** From 6da49b2d0331290400ec3f73b1849804188511d0 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 21 Dec 2020 17:50:09 +0100 Subject: [PATCH 07/25] WIP Co-authored-by: Dominik Voigt --- .../SmartConstrainedResizePolicy.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 6e7af7cec1b..40f1a0f0f6b 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumnBase; @@ -38,20 +39,21 @@ public Boolean call(TableView.ResizeFeatures prop) { resizableColumns = table.getVisibleLeafColumns().stream() .filter(TableColumnBase::isResizable) .collect(Collectors.toList()); - // This way, the proportions of the columns are kept during resize of the window - determineExpansionShare(); - - // Rearrange columns if content fits into displayed table - // Otherwise, the default is good enough if (contentFitsIntoTable(table)) { + LOGGER.debug("Content fits into table initially"); + LOGGER.debug("Rearranging columns if content fits into displayed table"); + // This way, the proportions of the columns are kept during resize of the window + determineExpansionShare(); return rearrangeColumns(table); + } else { + LOGGER.debug("Content too wide for displayed table. Using default alignment"); } return false; } else { if (!column.equals(lastModifiedColumn)) { lastModifiedColumn = column; // This way, the proportions of the columns are kept during resize of the window - determineExpansionShare(); + determineExpansionShareWithoutColumn(column); } return constrainedResize(prop); } @@ -68,9 +70,18 @@ private boolean contentFitsIntoTable(TableView table) { * Determines the share of the total width each column has */ private void determineExpansionShare() { + determineExpansionShare(resizableColumns.stream()); + } + + private void determineExpansionShareWithoutColumn(TableColumn excludedVisibleColumn) { + Stream> streamofColumns = resizableColumns.stream().filter(column -> !column.equals(excludedVisibleColumn)); + determineExpansionShare(streamofColumns); + } + + private void determineExpansionShare(Stream> streamofColumns) { // We need to store the initial preferred width, because "setWidth()" does not exist // There is only "setMinWidth", "setMaxWidth", and "setPrefWidth - Double allResizableColumnsWidth = resizableColumns.stream().mapToDouble(TableColumnBase::getPrefWidth).sum(); + Double allResizableColumnsWidth = streamofColumns.mapToDouble(TableColumnBase::getPrefWidth).sum(); for (TableColumnBase column : resizableColumns) { expansionShare.put(column, column.getPrefWidth() / allResizableColumnsWidth); } @@ -170,7 +181,7 @@ private void distributeDelta(TableView table, TableColumnBase userChosenColum .ifPresent(col::setPrefWidth); } newContentWidth = getContentWidth(table); - } while (Math.abs(tableWidth - newContentWidth) > 5d); + } while (Math.abs(tableWidth - newContentWidth) > 20d); } /** @@ -204,7 +215,7 @@ private Boolean rearrangeColumns(TableView table) { .ifPresent(col::setPrefWidth); } newContentWidth = getContentWidth(table); - } while (Math.abs(tableWidth - newContentWidth) > 5d); + } while (Math.abs(tableWidth - newContentWidth) > 20d); return (Math.floor(initialContentWidth - newContentWidth) != 0d); } From dfac7053ce9c97eb6b62c4889927b3bed8df3c0f Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 21 Dec 2020 17:53:53 +0100 Subject: [PATCH 08/25] Speedup processResources Co-authored-by: Dominik Voigt --- build.gradle | 4 ++-- gradle.properties | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 gradle.properties diff --git a/build.gradle b/build.gradle index 33165385d93..9db1d8b9dac 100644 --- a/build.gradle +++ b/build.gradle @@ -263,7 +263,7 @@ clean { processResources { filteringCharset = 'UTF-8' - filesMatching("build.properties") { + filesMatching("./build.properties") { expand(version: project.findProperty('projVersionInfo') ?: '100.0.0', "year": String.valueOf(Calendar.getInstance().get(Calendar.YEAR)), "authors": new File('AUTHORS').readLines().findAll { !it.startsWith("#") }.join(", "), @@ -279,7 +279,7 @@ processResources { filteringCharset = 'UTF-8' } - filesMatching("resource/**/meta.xml") { + filesMatching(["resources/resource/ods/meta.xml", "resources/resource/openoffice/meta.xml"]) { expand version: project.version } } diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000000..08b655db0e5 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.vs.watch=true From 8834fb67da6cd3fd695cc7a405df3236d7772be4 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 21 Dec 2020 19:28:34 +0100 Subject: [PATCH 09/25] Try path without ./ at the beginning Co-authored-by: Dominik Voigt --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9db1d8b9dac..9e997c586c2 100644 --- a/build.gradle +++ b/build.gradle @@ -263,7 +263,7 @@ clean { processResources { filteringCharset = 'UTF-8' - filesMatching("./build.properties") { + filesMatching("build.properties") { expand(version: project.findProperty('projVersionInfo') ?: '100.0.0', "year": String.valueOf(Calendar.getInstance().get(Calendar.YEAR)), "authors": new File('AUTHORS').readLines().findAll { !it.startsWith("#") }.join(", "), From 5b6f0d89074e1f1cf2082584937df337ea55544b Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 21 Dec 2020 19:55:15 +0100 Subject: [PATCH 10/25] Output java error on console, too Co-authored-by: Dominik Voigt --- src/main/java/org/jabref/gui/JabRefMain.java | 2 ++ src/main/java/org/jabref/logic/l10n/Localization.java | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/gui/JabRefMain.java b/src/main/java/org/jabref/gui/JabRefMain.java index 0afc8d7f720..42a46132d44 100644 --- a/src/main/java/org/jabref/gui/JabRefMain.java +++ b/src/main/java/org/jabref/gui/JabRefMain.java @@ -127,6 +127,8 @@ private static void ensureCorrectJavaVersion() { versionError.append(Localization.lang("Note that currently, JabRef does not run with Java 9.")); } + System.err.println(versionError.toString()); + FXDialog alert = new FXDialog(Alert.AlertType.ERROR, Localization.lang("Error"), true); alert.setHeaderText(null); alert.setContentText(versionError.toString()); diff --git a/src/main/java/org/jabref/logic/l10n/Localization.java b/src/main/java/org/jabref/logic/l10n/Localization.java index ba352b7239b..dcc612a9e20 100644 --- a/src/main/java/org/jabref/logic/l10n/Localization.java +++ b/src/main/java/org/jabref/logic/l10n/Localization.java @@ -51,7 +51,7 @@ private Localization() { public static String lang(String key, String... params) { if (localizedMessages == null) { // I'm logging this because it should never happen - LOGGER.error("Messages are not initialized before accessing key: " + key); + LOGGER.error("Messages are not initialized before accessing key: {}", key); setLanguage(Language.ENGLISH); } return lookup(localizedMessages, key, params); @@ -67,7 +67,7 @@ public static void setLanguage(Language language) { Optional knownLanguage = Language.convertToSupportedLocale(language); final Locale defaultLocale = Locale.getDefault(); if (knownLanguage.isEmpty()) { - LOGGER.warn("Language " + language + " is not supported by JabRef (Default:" + defaultLocale + ")"); + LOGGER.warn("Language {} is not supported by JabRef (Default: {})", language, defaultLocale); setLanguage(Language.ENGLISH); return; } @@ -143,7 +143,7 @@ private static String lookup(LocalizationBundle bundle, String key, String... pa String translation = bundle.containsKey(key) ? bundle.getString(key) : ""; if (translation.isEmpty()) { - LOGGER.warn("Warning: could not get translation for \"" + key + "\" for locale " + Locale.getDefault()); + LOGGER.warn("Warning: could not get translation for \"{}\" for locale {}", key, Locale.getDefault()); translation = key; } return new LocalizationKeyParams(translation, params).replacePlaceholders(); From fd80c30561b95d880912c33bec789a537f46e279 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 21 Dec 2020 19:56:51 +0100 Subject: [PATCH 11/25] Use LOGGER instead of System.err Co-authored-by: Dominik Voigt --- src/main/java/org/jabref/gui/JabRefMain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/JabRefMain.java b/src/main/java/org/jabref/gui/JabRefMain.java index 42a46132d44..8b95e150f3a 100644 --- a/src/main/java/org/jabref/gui/JabRefMain.java +++ b/src/main/java/org/jabref/gui/JabRefMain.java @@ -127,7 +127,7 @@ private static void ensureCorrectJavaVersion() { versionError.append(Localization.lang("Note that currently, JabRef does not run with Java 9.")); } - System.err.println(versionError.toString()); + LOGGER.error(versionError.toString()); FXDialog alert = new FXDialog(Alert.AlertType.ERROR, Localization.lang("Error"), true); alert.setHeaderText(null); From d15e90ff2e2767170fe45e0b8a9efce8670a36b6 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 21 Dec 2020 22:06:20 +0100 Subject: [PATCH 12/25] Fix scroll bar still present Co-authored-by: Dominik Voigt --- .../gui/maintable/SmartConstrainedResizePolicy.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 40f1a0f0f6b..342f48c1bbf 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -51,6 +51,7 @@ public Boolean call(TableView.ResizeFeatures prop) { return false; } else { if (!column.equals(lastModifiedColumn)) { + LOGGER.debug("Column changed"); lastModifiedColumn = column; // This way, the proportions of the columns are kept during resize of the window determineExpansionShareWithoutColumn(column); @@ -138,15 +139,18 @@ private Boolean constrainedResize(TableView.ResizeFeatures prop) { boolean columnsCanFitTable = currentTableContentWidth + delta < tableWidth; if (columnsCanFitTable) { LOGGER.debug("User window size in that way thus that window can contain all the columns"); + Optional newWidthOptional; if (currentTableContentWidth >= tableWidth) { LOGGER.debug("Before, the content did not fit. Now it fits. Rearrange everything."); - return rearrangeColumns(table); + newWidthOptional = determineNewWidth(userChosenColumnToResize, delta); + newWidthOptional.ifPresent(newWidth -> userChosenColumnToResize.setPrefWidth(newWidth)); + return newWidthOptional.isPresent(); } LOGGER.debug("Everything already fit. We distribute the delta now."); // Content does already fit // We "just" need to readjust the column widths - Optional newWidthOptional = determineNewWidth(userChosenColumnToResize, delta); + newWidthOptional = determineNewWidth(userChosenColumnToResize, delta); newWidthOptional.ifPresent(newWidth -> { distributeDelta(table, userChosenColumnToResize, newWidth); }); @@ -175,7 +179,7 @@ private void distributeDelta(TableView table, TableColumnBase userChosenColum LOGGER.debug("Distributing delta {}", remainingAvailableWidth); for (TableColumnBase col : columnsToResize) { double share = expansionShare.get(col); - determineNewWidth(col, share * remainingAvailableWidth) + determineNewWidth(col, Math.floor(share * remainingAvailableWidth)) // in case we can do something, do it // otherwise, the next loop iteration will distribute it .ifPresent(col::setPrefWidth); @@ -209,7 +213,7 @@ private Boolean rearrangeColumns(TableView table) { for (TableColumnBase col : resizableColumns) { double share = expansionShare.get(col); // Precondition in our case: col has to have minimum width - determineNewWidth(col, share * remainingAvailableWidth) + determineNewWidth(col, Math.floor(share * remainingAvailableWidth)) // in case we can do something, do it // otherwise, the next loop iteration will distribute it .ifPresent(col::setPrefWidth); From 7de21ad41602eb8ad08780f88dc0290a48c8a59f Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 21 Dec 2020 23:19:39 +0100 Subject: [PATCH 13/25] MinWidth to 80 Co-authored-by: Dominik Voigt --- src/main/java/org/jabref/gui/maintable/ColumnPreferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java b/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java index d56e7eb5ad5..662923e0731 100644 --- a/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java +++ b/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java @@ -4,7 +4,7 @@ public class ColumnPreferences { - public static final double DEFAULT_COLUMN_MIN_WIDTH = 10; + public static final double DEFAULT_COLUMN_MIN_WIDTH = 80; public static final double DEFAULT_COLUMN_WIDTH = 100; public static final double ICON_COLUMN_WIDTH = 16 + 12; // add some additional space to improve appearance From 80557f2fa7d1247527f7375ff210fbedee852610 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 22 Dec 2020 16:34:18 +0100 Subject: [PATCH 14/25] WIP (does not work) Co-authored-by: Dominik Voigt --- .../SmartConstrainedResizePolicy.java | 75 +++++++------------ 1 file changed, 27 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 342f48c1bbf..68287bdcd06 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -25,13 +25,13 @@ public class SmartConstrainedResizePolicy implements Callback expansionShare = new HashMap<>(); + private Map, Double> expansionShare = new HashMap<>(); List> resizableColumns; - private TableColumn lastModifiedColumn = null; + private TableColumn lastModifiedColumn = null; @Override public Boolean call(TableView.ResizeFeatures prop) { - TableColumn column = prop.getColumn(); + TableColumn column = prop.getColumn(); if (column == null) { // happens at initialization and at window resize LOGGER.debug("Table is fully rendered"); @@ -61,10 +61,7 @@ public Boolean call(TableView.ResizeFeatures prop) { } private boolean contentFitsIntoTable(TableView table) { - Double tableWidth = getContentWidth(table); - List> visibleLeafColumns = table.getVisibleLeafColumns(); - Double currentTableContentWidth = visibleLeafColumns.stream().mapToDouble(TableColumnBase::getWidth).sum(); - return tableWidth >= currentTableContentWidth; + return table.getWidth() >= getContentWidth(table); } /** @@ -129,22 +126,18 @@ private Optional determineNewWidth(TableColumnBase column, Double private Boolean constrainedResize(TableView.ResizeFeatures prop) { TableView table = prop.getTable(); Double tableWidth = getContentWidth(table); - List> visibleLeafColumns = table.getVisibleLeafColumns(); - // The current able content width contains all visible columns: the resizable ones and the non-resizable ones - Double currentTableContentWidth = visibleLeafColumns.stream().mapToDouble(TableColumnBase::getWidth).sum(); + Double currentTableContentWidth = getContentWidth(table); Double delta = prop.getDelta(); TableColumn userChosenColumnToResize = prop.getColumn(); boolean columnsCanFitTable = currentTableContentWidth + delta < tableWidth; if (columnsCanFitTable) { - LOGGER.debug("User window size in that way thus that window can contain all the columns"); + LOGGER.debug("User changed window size in a way that window can contain all the columns"); Optional newWidthOptional; if (currentTableContentWidth >= tableWidth) { LOGGER.debug("Before, the content did not fit. Now it fits. Rearrange everything."); - newWidthOptional = determineNewWidth(userChosenColumnToResize, delta); - newWidthOptional.ifPresent(newWidth -> userChosenColumnToResize.setPrefWidth(newWidth)); - return newWidthOptional.isPresent(); + return rearrangeColumns(table); } LOGGER.debug("Everything already fit. We distribute the delta now."); @@ -166,26 +159,8 @@ private Boolean constrainedResize(TableView.ResizeFeatures prop) { private void distributeDelta(TableView table, TableColumnBase userChosenColumnToResize, Double newWidth) { userChosenColumnToResize.setPrefWidth(newWidth); - List> columnsToResize = resizableColumns.stream().filter(col -> !col.equals(userChosenColumnToResize)).collect(Collectors.toList()); - - Double tableWidth = table.getWidth(); - Double newContentWidth; - - do - { - // in case the userChosenColumnToResize got bigger, the remaining available width will get below 0 --> other columns need to be shrunk - Double remainingAvailableWidth = tableWidth - getContentWidth(table); - LOGGER.debug("Distributing delta {}", remainingAvailableWidth); - for (TableColumnBase col : columnsToResize) { - double share = expansionShare.get(col); - determineNewWidth(col, Math.floor(share * remainingAvailableWidth)) - // in case we can do something, do it - // otherwise, the next loop iteration will distribute it - .ifPresent(col::setPrefWidth); - } - newContentWidth = getContentWidth(table); - } while (Math.abs(tableWidth - newContentWidth) > 20d); + rearrangeColumns(table, columnsToResize); } /** @@ -204,24 +179,34 @@ private Boolean rearrangeColumns(TableView table) { col.setPrefWidth(col.getMinWidth()); } + return (initialContentWidth - getContentWidth(table)) != 0d; + } + + private void rearrangeColumns(TableView table, List> columnsToResize) { Double tableWidth = table.getWidth(); Double newContentWidth; + do { - Double remainingAvailableWidth = Math.max(0, tableWidth - getContentWidth(table)); - LOGGER.debug("Distributing delta {}", remainingAvailableWidth); - for (TableColumnBase col : resizableColumns) { + // in case the userChosenColumnToResize got bigger, the remaining available width will get below 0 --> other columns need to be shrunk + LOGGER.debug("tableWidth {}", tableWidth); + Double contentWidth = getContentWidth(table); + LOGGER.debug("contentWidth {}", contentWidth); + Double remainingAvailableWidth = tableWidth - contentWidth; + // Double remainingAvailableWidth = Math.max(0, tableWidth - contentWidth); + LOGGER.debug("Distributing remainingAvailableWidth {}", remainingAvailableWidth); + for (TableColumnBase col : columnsToResize) { double share = expansionShare.get(col); // Precondition in our case: col has to have minimum width - determineNewWidth(col, Math.floor(share * remainingAvailableWidth)) + determineNewWidth(col, share * remainingAvailableWidth) // in case we can do something, do it // otherwise, the next loop iteration will distribute it .ifPresent(col::setPrefWidth); } newContentWidth = getContentWidth(table); - } while (Math.abs(tableWidth - newContentWidth) > 20d); - - return (Math.floor(initialContentWidth - newContentWidth) != 0d); + LOGGER.debug("newContentWidth {}", newContentWidth); + LOGGER.debug("tableWidth - newContentWidth = {}", tableWidth - newContentWidth); + } while (tableWidth - newContentWidth > 20d); } /** @@ -235,13 +220,7 @@ private Double getSumMinWidthOfColumns(TableView table) { * Computes and returns the width required by the content of the table */ private Double getContentWidth(TableView table) { - try { - // TODO: reflective access, should be removed - Field privateStringField = TableView.class.getDeclaredField("contentWidth"); - privateStringField.setAccessible(true); - return (Double) privateStringField.get(table); - } catch (IllegalAccessException | NoSuchFieldException e) { - return 0d; - } + // The current table content width contains all visible columns: the resizable ones and the non-resizable ones + return table.getVisibleLeafColumns().stream().mapToDouble(TableColumnBase::getWidth).sum(); } } From c3b02e3020c99e1d65f8a76c29fd7419cd9d34d8 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 29 Dec 2020 11:51:00 +0100 Subject: [PATCH 15/25] Fix ordering in CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0091e9a1fb3..825f233c60c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Changed +- We changed the resize behavior of table columns to have smart fit into the table if there is enough space. [#967](https://github.com/JabRef/jabref/issues/967) + ### Fixed - We fixed an issue with the style of highlighted check boxes while searching in preferences. [#7226](https://github.com/JabRef/jabref/issues/7226) @@ -59,7 +61,6 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We changed the way JabRef displays the title of a tab and of the window. [4161](https://github.com/JabRef/jabref/issues/4161) - We changed connect timeouts for server requests to 30 seconds in general and 5 seconds for GROBID server (special) and improved user notifications on connection issues. [7026](https://github.com/JabRef/jabref/pull/7026) - We changed the order of the library tab context menu items. [#7171](https://github.com/JabRef/jabref/issues/7171) -- We changed the resize behavior of table columns to have smart fit into the table if there is enough space. [#967](https://github.com/JabRef/jabref/issues/967) - We renamed "Show extra columns" to "Show dedicated file columns". [#7181](https://github.com/JabRef/jabref/pull/7181) - We changed the way linked files are opened on Linux to use the native openFile method, compatible with confined packages. [7037](https://github.com/JabRef/jabref/pull/7037) - We refined the entry preview to show the full names of authors and editors, to list the editor only if no author is present, have the year earlier. [#7083](https://github.com/JabRef/jabref/issues/7083) From fd118a6d5428fd8096563eeb8b0121e0e9253f16 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 29 Dec 2020 17:28:21 +0100 Subject: [PATCH 16/25] WIP Co-authored-by: Dominik Voigt --- .../SmartConstrainedResizePolicy.java | 84 +++++++++++++------ 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 68287bdcd06..6b89db21348 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -1,6 +1,5 @@ package org.jabref.gui.maintable; -import java.lang.reflect.Field; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,52 +24,77 @@ public class SmartConstrainedResizePolicy implements Callback, Double> expansionShare = new HashMap<>(); + List> resizableColumns; private TableColumn lastModifiedColumn = null; @Override public Boolean call(TableView.ResizeFeatures prop) { TableColumn column = prop.getColumn(); + Boolean result; if (column == null) { // happens at initialization and at window resize LOGGER.debug("Table is fully rendered"); - TableView table = prop.getTable(); - resizableColumns = table.getVisibleLeafColumns().stream() - .filter(TableColumnBase::isResizable) - .collect(Collectors.toList()); - if (contentFitsIntoTable(table)) { - LOGGER.debug("Content fits into table initially"); - LOGGER.debug("Rearranging columns if content fits into displayed table"); - // This way, the proportions of the columns are kept during resize of the window - determineExpansionShare(); - return rearrangeColumns(table); - } else { - LOGGER.debug("Content too wide for displayed table. Using default alignment"); - } + result = doInitialTableRendering(prop); + } else { + LOGGER.debug("Column width changed"); + result = doColumnRearrangement(prop, column); + } + LOGGER.debug("Result: {}", result); + return result; + } + + private Boolean doInitialTableRendering(TableView.ResizeFeatures prop) { + TableView table = prop.getTable(); + if (table.getWidth() == 0.0d) { + LOGGER.debug("Table width is 0. Returning false"); return false; + } + resizableColumns = table.getVisibleLeafColumns().stream() + .filter(TableColumnBase::isResizable) + .collect(Collectors.toList()); + if (contentFitsIntoTable(table)) { + LOGGER.debug("Content fits into table initially"); + determineExpansionShare(); + LOGGER.debug("Rearranging columns"); + return rearrangeColumns(table); } else { - if (!column.equals(lastModifiedColumn)) { - LOGGER.debug("Column changed"); - lastModifiedColumn = column; - // This way, the proportions of the columns are kept during resize of the window - determineExpansionShareWithoutColumn(column); - } - return constrainedResize(prop); + LOGGER.debug("Content too wide for displayed table. Using default alignment"); + } + return false; + } + + private Boolean doColumnRearrangement(TableView.ResizeFeatures prop, TableColumn column) { + if (!column.equals(lastModifiedColumn)) { + LOGGER.debug("Column changed"); + lastModifiedColumn = column; + determineExpansionShareWithoutColumn(column); } + return constrainedResize(prop); } private boolean contentFitsIntoTable(TableView table) { - return table.getWidth() >= getContentWidth(table); + double tableWidth = table.getWidth(); + Double contentWidth = getContentWidth(table); + boolean comparisonResult = tableWidth >= contentWidth; + LOGGER.debug("tableWidth {} >= contentWidth {}: {}", tableWidth, contentWidth, comparisonResult); + return comparisonResult; } /** - * Determines the share of the total width each column has + * Determines the share of the total width each column has. + * This way, the proportions of the columns are kept during resize of the window */ private void determineExpansionShare() { determineExpansionShare(resizableColumns.stream()); } + /** + * This way, the proportions of the columns are kept during resize of the window + */ private void determineExpansionShareWithoutColumn(TableColumn excludedVisibleColumn) { Stream> streamofColumns = resizableColumns.stream().filter(column -> !column.equals(excludedVisibleColumn)); determineExpansionShare(streamofColumns); @@ -98,6 +122,8 @@ private void determineExpansionShare(Stream> streamo private Optional determineNewWidth(TableColumnBase column, Double delta) { // This is com.sun.javafx.scene.control.skin.Utils.boundedSize with more comments and Optionals + LOGGER.trace("Column {}", column.getText()); + // Calculate newWidth based on delta and constraint of the column double oldWidth = column.getWidth(); double newWidth; @@ -132,8 +158,9 @@ private Boolean constrainedResize(TableView.ResizeFeatures prop) { TableColumn userChosenColumnToResize = prop.getColumn(); boolean columnsCanFitTable = currentTableContentWidth + delta < tableWidth; + LOGGER.debug("currentTableContentWidth {} + delta {} < tableWidth {} = {}", currentTableContentWidth, delta, tableWidth, columnsCanFitTable); if (columnsCanFitTable) { - LOGGER.debug("User changed window size in a way that window can contain all the columns"); + LOGGER.debug("User changed column size in a way that window can contain all the columns"); Optional newWidthOptional; if (currentTableContentWidth >= tableWidth) { LOGGER.debug("Before, the content did not fit. Now it fits. Rearrange everything."); @@ -168,6 +195,7 @@ private void distributeDelta(TableView table, TableColumnBase userChosenColum */ private Boolean rearrangeColumns(TableView table) { Double initialContentWidth = getContentWidth(table); + LOGGER.debug("initialContentWidth {}", initialContentWidth); // Implementation idea: // Each column has to have at least the minimum width @@ -178,6 +206,10 @@ private Boolean rearrangeColumns(TableView table) { for (TableColumnBase col : resizableColumns) { col.setPrefWidth(col.getMinWidth()); } + LOGGER.debug("Width after setting min width: {}", getContentWidth(table)); + + rearrangeColumns(table, resizableColumns); + LOGGER.debug("Width after rearranging columns: {}", getContentWidth(table)); return (initialContentWidth - getContentWidth(table)) != 0d; } @@ -198,7 +230,9 @@ private void rearrangeColumns(TableView table, List col : columnsToResize) { double share = expansionShare.get(col); // Precondition in our case: col has to have minimum width - determineNewWidth(col, share * remainingAvailableWidth) + double delta = share * remainingAvailableWidth; + LOGGER.debug("share {} * remainingAvailableWidth {} = delta {}", share, remainingAvailableWidth, delta); + determineNewWidth(col, delta) // in case we can do something, do it // otherwise, the next loop iteration will distribute it .ifPresent(col::setPrefWidth); From f0788f647567e90cd82f8f62a27b9f0769b8bd97 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 29 Dec 2020 21:07:37 +0100 Subject: [PATCH 17/25] WIP - "User changed column size in a way that window can contain all the columns" still a TODO Co-authored-by: Dominik Voigt --- .../SmartConstrainedResizePolicy.java | 41 ++++++++++--------- src/main/resources/log4j2.xml | 3 ++ 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 6b89db21348..c6213d63826 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -5,7 +5,6 @@ import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; -import java.util.stream.Stream; import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumnBase; @@ -41,7 +40,7 @@ public Boolean call(TableView.ResizeFeatures prop) { result = doInitialTableRendering(prop); } else { LOGGER.debug("Column width changed"); - result = doColumnRearrangement(prop, column); + result = doColumnRearrangement(prop); } LOGGER.debug("Result: {}", result); return result; @@ -67,7 +66,8 @@ private Boolean doInitialTableRendering(TableView.ResizeFeatures prop) { return false; } - private Boolean doColumnRearrangement(TableView.ResizeFeatures prop, TableColumn column) { + private Boolean doColumnRearrangement(TableView.ResizeFeatures prop) { + TableColumn column = prop.getColumn(); if (!column.equals(lastModifiedColumn)) { LOGGER.debug("Column changed"); lastModifiedColumn = column; @@ -89,23 +89,26 @@ private boolean contentFitsIntoTable(TableView table) { * This way, the proportions of the columns are kept during resize of the window */ private void determineExpansionShare() { - determineExpansionShare(resizableColumns.stream()); + determineExpansionShare(resizableColumns); } /** * This way, the proportions of the columns are kept during resize of the window */ private void determineExpansionShareWithoutColumn(TableColumn excludedVisibleColumn) { - Stream> streamofColumns = resizableColumns.stream().filter(column -> !column.equals(excludedVisibleColumn)); - determineExpansionShare(streamofColumns); + List> columns = resizableColumns.stream().filter(column -> !column.equals(excludedVisibleColumn)).collect(Collectors.toList()); + determineExpansionShare(columns); } - private void determineExpansionShare(Stream> streamofColumns) { + private void determineExpansionShare(List> columns) { // We need to store the initial preferred width, because "setWidth()" does not exist // There is only "setMinWidth", "setMaxWidth", and "setPrefWidth - Double allResizableColumnsWidth = streamofColumns.mapToDouble(TableColumnBase::getPrefWidth).sum(); - for (TableColumnBase column : resizableColumns) { - expansionShare.put(column, column.getPrefWidth() / allResizableColumnsWidth); + Double allColumnsWidth = columns.stream().mapToDouble(TableColumnBase::getPrefWidth).sum(); + LOGGER.debug("allColumnsWidth: {}", allColumnsWidth); + for (TableColumnBase column : columns) { + double share = column.getPrefWidth() / allColumnsWidth; + LOGGER.debug("share of {}: {}", column.getText(), share); + expansionShare.put(column, share); } } @@ -150,13 +153,12 @@ private Optional determineNewWidth(TableColumnBase column, Double * In all cases the minimum/maximum width of each column is respected. */ private Boolean constrainedResize(TableView.ResizeFeatures prop) { + TableColumn userChosenColumnToResize = prop.getColumn(); + TableView table = prop.getTable(); - Double tableWidth = getContentWidth(table); + Double tableWidth = table.getWidth(); Double currentTableContentWidth = getContentWidth(table); Double delta = prop.getDelta(); - - TableColumn userChosenColumnToResize = prop.getColumn(); - boolean columnsCanFitTable = currentTableContentWidth + delta < tableWidth; LOGGER.debug("currentTableContentWidth {} + delta {} < tableWidth {} = {}", currentTableContentWidth, delta, tableWidth, columnsCanFitTable); if (columnsCanFitTable) { @@ -227,15 +229,16 @@ private void rearrangeColumns(TableView table, List col : columnsToResize) { - double share = expansionShare.get(col); - // Precondition in our case: col has to have minimum width + for (TableColumnBase column : columnsToResize) { + LOGGER.debug("Column {}", column.getText()); + double share = expansionShare.get(column); + // Precondition in our case: column has to have minimum width double delta = share * remainingAvailableWidth; LOGGER.debug("share {} * remainingAvailableWidth {} = delta {}", share, remainingAvailableWidth, delta); - determineNewWidth(col, delta) + determineNewWidth(column, delta) // in case we can do something, do it // otherwise, the next loop iteration will distribute it - .ifPresent(col::setPrefWidth); + .ifPresent(column::setPrefWidth); } newContentWidth = getContentWidth(table); LOGGER.debug("newContentWidth {}", newContentWidth); diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index f9e6c0bf60b..98202e744f3 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -6,6 +6,9 @@ + + + From a1633a198170f1298ef906f1478acca8b907c34f Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sat, 2 Jan 2021 22:12:40 +0100 Subject: [PATCH 18/25] Add documentation on implementation idea Co-authored-by: Dominik Voigt --- .../SmartConstrainedResizePolicy.java | 148 ++++++++++++++++-- 1 file changed, 131 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index c6213d63826..49fa796a124 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -1,6 +1,7 @@ package org.jabref.gui.maintable; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -15,9 +16,99 @@ import org.slf4j.LoggerFactory; /** - * This resize policy is almost the same as {@link TableView#CONSTRAINED_RESIZE_POLICY} - * We make sure that the width of all columns sums up to the total width of the table. - * However, in contrast to {@link TableView#CONSTRAINED_RESIZE_POLICY} we size the columns initially by their preferred width. + * + * This resize policy supports following properties – preferably user-configurable: + * + *
    + *
  • Honoring the minimal, (initial) preferred, and maximal width for each column
  • + *
  • Honoring non-resizable columns (i.e., that a column should not be scaled)
  • + *
  • Preferred width derivation (e.g., 80% of the (initial) preferred width) is a point from which on the column should not be shrunk any more
  • + *
+ * + * This resize policy supports following properties (non user-configurable): + * + *
    + *
  • Between each resizing, the properties of each column (minimal width, resizable, ...) can change.
  • + *
  • Automatic sizing of the columns: + *
      + *
    • Each column should have at least its minimal size.
    • + *
    • If the space honoring the minimal width is not sufficient, a horizontal scrollbar should be used.
    • + *
    • Columns with fixed size should use those fixed sizes
    • + *
    • If there is additional space left the remaining columns should be broadened to use this space
    • + *
    + *
  • + *
  • The displayed table should fit into the table space. We accept that columns do not have their preferred width then.
  • + *
  • Ideally, the columns should have the preferred width
  • + *
  • If a user changes the size of a column, the new size of that column should be set.
  • + *
  • No behavior toggle buttons by the user --> this policy is a smart one
  • + *
  • In case a user shrinks a column: + *
      + *
    • The column shrinks by the requested delta.
    • + *
    • The column must not shrink below the minimum width.
    • + *
    • Thereby, the (initial) preferred width is not changed.
    • + *
    + *
  • + *
  • In case a user enlarges a column: + *
      If the
    + *
  • + *
  • Columns being the farest away from their (initial) preferred width are scaled the most
  • + *
+ * + * The implementation is driven by following factors: + * + *
    + *
  • Minimal width is an "absolute" minimal making the content nearly barely visible
  • + *
  • Maximal width is an "absolute" maximal width making the content too much visible
  • + *
  • Preferred width is set to a reasonable value
  • + *
  • + *
+ * + * Notes: + * + *
    + *
  • Initial preferred with is the preferred width
  • + *
  • Rescaling changes the actual width of the other columns according to their current ratio. This way, the column proportions are respected.
  • + *
  • Resizing of column + *
  • If enlarged + *
      + *
    • Content has fit into table. Then, content does not fit into table (because of delta). Column gets enlarged by the delta. Other columns should not below a certain "reasonable" size ("threshold"). All columns are shrunk until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
    • + *
    • Content has not fit into table. Then, content still does not fit into table (because of delta). Column gets enlarged by the delta. Other columns are not changed. Scrollbar gets wider.
    • + *
    + *
  • + *
  • If shrunk + *
      + *
    • Content has fit into table. Then, content still fits into table (because of delta). Column must not shrink below minimum width. If minimum is reached, nothing happens. Remaining delta is distributed among other columns. No scrollbar present.
    • + *
    • Content has not fit into table. If the delta is applied, the content fits into the table. Delta is applied to the column. Remaining delta splits up of delta-in-bounds and delta-out-of-bounds. Delta-in-bounds is distributed among other columns. Scrollbar disappears.
    • + *
    • Content has not fit into table. If the delta is applied, the content still does not fit into table. Column is shrunk respecting the delta. Other columns are resized. Scrollbar shrinks.
    • + *
    + *
  • + * + *
  • Resizing of window + *
      + *
    • If enlarged + *
        + *
      • Content has fit into table. Then, content still has to fit into table. Thus, enlarge all columns respecting the ratio (the content is enlarged proportional to actual width). Scrollbar not present.
      • + *
      • Content has not fit into table. Case: Content still not fits into table. No resize action. Scrollbar shrinks.
      • + *
      • Content has not fit into table. Case: Content fits into table. Calculate the delta and distribute across all resizable columns using the ratio. Scrollbar not present any more.
      • + *
      + *
    • + *
    • If shrunk + *
        + *
      • Content has fit into table. Content does not fit into table anymore (because of shrinkage). Delta is absorbed by columns until threshold is reached. If not complete delta can be absorbed, scrollbar appears.
      • + *
      • Content has not fit into table. Content still does not fit into table (because of shrinkage). Scrollbar enlarges.
      • + *
      + *
    • + *
    + *
  • + *
+ * + *

Related Work

+ *
    + *
  • TableView SmartResize Policy
  • + *
  • CONSTRAINED_RESIZE_POLICY: This policy a) initially adjust the column widths of all columns in a way that the table fits the whole table space, b) adjusts the width of the columns right to the current column to have the whole table fit into the table space. The polciy starts with the minimum width, not with the preferred width. The policy does not support the case if the content does not fit into the table space.
  • + *
  • UNCONSTRAINED_RESIZE_POLICY: This policy just resizes the specified column by the provided delta and shifts all other columns (to the right of the given column) further to the right (when the delta is positive) or to the left (when the delta is negative).
  • + *
  • HypnosResizePolicy: Similar to CONSTRAINED_RESIZE_POLICY. However, at resize extra space is given to columns that aren't at their pref width and need that type of space (negative or positive) on a proportional basis first.
  • + *
*/ public class SmartConstrainedResizePolicy implements Callback { @@ -57,7 +148,6 @@ private Boolean doInitialTableRendering(TableView.ResizeFeatures prop) { .collect(Collectors.toList()); if (contentFitsIntoTable(table)) { LOGGER.debug("Content fits into table initially"); - determineExpansionShare(); LOGGER.debug("Rearranging columns"); return rearrangeColumns(table); } else { @@ -66,16 +156,6 @@ private Boolean doInitialTableRendering(TableView.ResizeFeatures prop) { return false; } - private Boolean doColumnRearrangement(TableView.ResizeFeatures prop) { - TableColumn column = prop.getColumn(); - if (!column.equals(lastModifiedColumn)) { - LOGGER.debug("Column changed"); - lastModifiedColumn = column; - determineExpansionShareWithoutColumn(column); - } - return constrainedResize(prop); - } - private boolean contentFitsIntoTable(TableView table) { double tableWidth = table.getWidth(); Double contentWidth = getContentWidth(table); @@ -101,6 +181,7 @@ private void determineExpansionShareWithoutColumn(TableColumn excludedVisibleCol } private void determineExpansionShare(List> columns) { + expansionShare.clear(); // We need to store the initial preferred width, because "setWidth()" does not exist // There is only "setMinWidth", "setMaxWidth", and "setPrefWidth Double allColumnsWidth = columns.stream().mapToDouble(TableColumnBase::getPrefWidth).sum(); @@ -110,6 +191,9 @@ private void determineExpansionShare(List> columns) LOGGER.debug("share of {}: {}", column.getText(), share); expansionShare.put(column, share); } + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Sum of shares: {}", expansionShare.values().stream().mapToDouble(Double::doubleValue).sum()); + } } /** @@ -139,6 +223,7 @@ private Optional determineNewWidth(TableColumnBase column, Double LOGGER.trace("MaxWidth {}", maxWidth); newWidth = Math.min(maxWidth, oldWidth + delta); } + newWidth = Math.floor(newWidth) - 2d; LOGGER.trace("Size: {} -> {}", oldWidth, newWidth); if (oldWidth == newWidth) { return Optional.empty(); @@ -152,7 +237,7 @@ private Optional determineNewWidth(TableColumnBase column, Double * In case the content does not fit anymore, a horizontal scroll bar is shown. * In all cases the minimum/maximum width of each column is respected. */ - private Boolean constrainedResize(TableView.ResizeFeatures prop) { + private Boolean doColumnRearrangement(TableView.ResizeFeatures prop) { TableColumn userChosenColumnToResize = prop.getColumn(); TableView table = prop.getTable(); @@ -170,6 +255,13 @@ private Boolean constrainedResize(TableView.ResizeFeatures prop) { } LOGGER.debug("Everything already fit. We distribute the delta now."); + TableColumn column = prop.getColumn(); + if (!column.equals(lastModifiedColumn)) { + LOGGER.debug("Column changed"); + lastModifiedColumn = column; + determineExpansionShareWithoutColumn(column); + } + // Content does already fit // We "just" need to readjust the column widths newWidthOptional = determineNewWidth(userChosenColumnToResize, delta); @@ -196,6 +288,8 @@ private void distributeDelta(TableView table, TableColumnBase userChosenColum * Rearranges the widths of all columns according to their shares (proportional to their preferred widths) */ private Boolean rearrangeColumns(TableView table) { + determineExpansionShare(); + Double initialContentWidth = getContentWidth(table); LOGGER.debug("initialContentWidth {}", initialContentWidth); @@ -220,6 +314,7 @@ private void rearrangeColumns(TableView table, List other columns need to be shrunk @@ -242,8 +337,27 @@ private void rearrangeColumns(TableView table, List 20d); + remainingPixels = tableWidth - newContentWidth; + LOGGER.debug("tableWidth - newContentWidth = {}", remainingPixels); + } while (remainingPixels > 20d); + // in case the table has less than 7 pixels, a scroll bar is there + // quickfix by removing pixels + double amountOfRemainingPixelsRequiredForNoScrollBar = 7.0; + if (remainingPixels < amountOfRemainingPixelsRequiredForNoScrollBar) { + double pixelsToRemove = amountOfRemainingPixelsRequiredForNoScrollBar - remainingPixels; + Iterator> iterator = columnsToResize.iterator(); + while (pixelsToRemove > 0) { + if (!iterator.hasNext()) { + iterator = columnsToResize.iterator(); + } + TableColumn column = iterator.next(); + Optional aDouble = determineNewWidth(column, -1.0); + if (aDouble.isPresent()) { + column.setPrefWidth(aDouble.get()); + pixelsToRemove--; + } + } + } } /** From 73d5448eeacbaf34fc892893eda6bf7002e18320 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 3 Jan 2021 17:47:34 +0100 Subject: [PATCH 19/25] Refine description (and change TableColumnBase to TableColumn) Co-authored-by: Dominik Voigt --- .../SmartConstrainedResizePolicy.java | 182 ++++++++++++------ 1 file changed, 119 insertions(+), 63 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 49fa796a124..ecf5c75b802 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -1,5 +1,6 @@ package org.jabref.gui.maintable; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -8,7 +9,6 @@ import java.util.stream.Collectors; import javafx.scene.control.TableColumn; -import javafx.scene.control.TableColumnBase; import javafx.scene.control.TableView; import javafx.util.Callback; @@ -20,12 +20,13 @@ * This resize policy supports following properties – preferably user-configurable: * *
    - *
  • Honoring the minimal, (initial) preferred, and maximal width for each column
  • + *
  • Honoring the minimal and maximal width for each column
  • + *
  • Honoring a desired width of each column (if available). It is used at initial table rendering (e.g., program startup) and for the threshold (see below). It is not used in other cases.
  • *
  • Honoring non-resizable columns (i.e., that a column should not be scaled)
  • - *
  • Preferred width derivation (e.g., 80% of the (initial) preferred width) is a point from which on the column should not be shrunk any more
  • + *
  • Desired width derivation (e.g., 80% of the desired width) is a point from which on the column should not be shrunk any more. This is called the threshold.
  • *
* - * This resize policy supports following properties (non user-configurable): + * This resize policy supports following properties (non user-configurable): * *
    *
  • Between each resizing, the properties of each column (minimal width, resizable, ...) can change.
  • @@ -37,21 +38,20 @@ *
  • If there is additional space left the remaining columns should be broadened to use this space
  • *
* - *
  • The displayed table should fit into the table space. We accept that columns do not have their preferred width then.
  • - *
  • Ideally, the columns should have the preferred width
  • + *
  • We balance between a complete fit into the table space and the desired width. A "huge" derivation from the desired width is not accepted.
  • + *
  • Ideally, the columns should have the desired width
  • *
  • If a user changes the size of a column, the new size of that column should be set.
  • *
  • No behavior toggle buttons by the user --> this policy is a smart one
  • - *
  • In case a user shrinks a column: + *
  • We distinguish between a column being resized by the user and a column automatically resized.
  • + *
  • In case a user changes a column manually: *
      - *
    • The column shrinks by the requested delta.
    • - *
    • The column must not shrink below the minimum width.
    • - *
    • Thereby, the (initial) preferred width is not changed.
    • + *
    • The column shrinks/enlarges by the requested delta.
    • + *
    • The column must not shrink below the minimum width / enlarge above the maximum width.
    • + *
    • Thereby, the desired width is not changed.
    • + *
    • The other columns adapt "smartly": The other columns according to their current ratio. This way, the column proportions are respected.
    • *
    *
  • - *
  • In case a user enlarges a column: - *
      If the
    - *
  • - *
  • Columns being the farest away from their (initial) preferred width are scaled the most
  • + *
  • The ratio used for enlargement of columns respects the current column width. The ratio for shrinkage is constant for all columns.
  • * * * The implementation is driven by following factors: @@ -59,53 +59,67 @@ *
      *
    • Minimal width is an "absolute" minimal making the content nearly barely visible
    • *
    • Maximal width is an "absolute" maximal width making the content too much visible
    • - *
    • Preferred width is set to a reasonable value
    • - *
    • + *
    • The preferred width holds the current active width of a column (due to JavaFX design decisions).
    • + *
    • Desired width is set to a reasonable value. The desired width is a "globally" preferred width.
    • + *
    + * + *

    Resizing of column

    + * + *
      + *
    • If enlarged + *
        + *
      • Content has fit into table. Then, content does not fit into table (because of delta). Column gets enlarged by the delta. Other columns should not below a certain "reasonable" size ("threshold"). All columns are shrunk (all equally) until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
      • + *
      • Content has not fit into table. Then, content still does not fit into table (because of delta). Column gets enlarged by the delta. Other columns are not changed. Scrollbar gets wider.
      • + *
      + *
    • + *
    • If shrunk + *
        + *
      • Content has fit into table. Then, content still fits into table (because of delta). Column must not shrink below minimum width. If minimum is reached, nothing happens. Remaining delta is distributed among other columns. No scrollbar present.
      • + *
      • Content has not fit into table. If the delta is applied, the content fits into the table. Delta is applied to the column. Remaining delta splits up of delta-in-bounds and delta-out-of-bounds. Delta-in-bounds is distributed among other columns. Scrollbar disappears.
      • + *
      • Content has not fit into table. If the delta is applied, the content still does not fit into table. Column is shrunk respecting the delta. Other columns are resized. Scrollbar shrinks.
      • + *
      + *
    • *
    * - * Notes: + *

    Resizing of window

    * *
      - *
    • Initial preferred with is the preferred width
    • - *
    • Rescaling changes the actual width of the other columns according to their current ratio. This way, the column proportions are respected.
    • - *
    • Resizing of column - *
    • If enlarged - *
        - *
      • Content has fit into table. Then, content does not fit into table (because of delta). Column gets enlarged by the delta. Other columns should not below a certain "reasonable" size ("threshold"). All columns are shrunk until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
      • - *
      • Content has not fit into table. Then, content still does not fit into table (because of delta). Column gets enlarged by the delta. Other columns are not changed. Scrollbar gets wider.
      • - *
      - *
    • - *
    • If shrunk - *
        - *
      • Content has fit into table. Then, content still fits into table (because of delta). Column must not shrink below minimum width. If minimum is reached, nothing happens. Remaining delta is distributed among other columns. No scrollbar present.
      • - *
      • Content has not fit into table. If the delta is applied, the content fits into the table. Delta is applied to the column. Remaining delta splits up of delta-in-bounds and delta-out-of-bounds. Delta-in-bounds is distributed among other columns. Scrollbar disappears.
      • - *
      • Content has not fit into table. If the delta is applied, the content still does not fit into table. Column is shrunk respecting the delta. Other columns are resized. Scrollbar shrinks.
      • - *
      - *
    • + *
    • If enlarged + *
        + *
      • Content has fit into table. Then, content still has to fit into table. Thus, enlarge all columns respecting the ratio (the content is enlarged proportional to actual width). Scrollbar not present.
      • + *
      • Content has not fit into table. Case: Content still not fits into table. No resize action. Scrollbar shrinks.
      • + *
      • Content has not fit into table. Case: Content fits into table. Calculate the delta and distribute across all resizable columns using the ratio. Scrollbar not present any more.
      • + *
      *
    • - *
    • Resizing of window + *
    • If shrunk *
        - *
      • If enlarged - *
          - *
        • Content has fit into table. Then, content still has to fit into table. Thus, enlarge all columns respecting the ratio (the content is enlarged proportional to actual width). Scrollbar not present.
        • - *
        • Content has not fit into table. Case: Content still not fits into table. No resize action. Scrollbar shrinks.
        • - *
        • Content has not fit into table. Case: Content fits into table. Calculate the delta and distribute across all resizable columns using the ratio. Scrollbar not present any more.
        • - *
        - *
      • - *
      • If shrunk - *
          - *
        • Content has fit into table. Content does not fit into table anymore (because of shrinkage). Delta is absorbed by columns until threshold is reached. If not complete delta can be absorbed, scrollbar appears.
        • - *
        • Content has not fit into table. Content still does not fit into table (because of shrinkage). Scrollbar enlarges.
        • - *
        - *
      • + *
      • Content has fit into table. Content does not fit into table anymore (because of shrinkage). Delta is absorbed by columns until threshold is reached. If not complete delta can be absorbed, scrollbar appears.
      • + *
      • Content has not fit into table. Content still does not fit into table (because of shrinkage). Scrollbar enlarges.
      • *
      *
    • *
    * + *

    I0 - Initial table rendering

    + * + * Decision to take: a) Use last setting or b) rerender with desired widths. We opt for b), because we assume that user-configuration of desired widths is easy. + * + *
      + *
    • I1 - Initially, all columns takes the desired width.
    • + *
    • I2 - If content fits into table, distribute remaining delta to table space according to the shares. No scollbar.
    • + *
    • I3 - If content does not fit into table, all columns are shrunk (all equally) until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
    • + *
    + * + *

    Notes

    + * + *
      + *
    • Design goal of this class is to be self-contained and not dependent on other non-JavaFX classes.
    • + *
    • TODO: In case the desired column width is updated, the SmartConstrainedResizePolicy needs to be reinstantiated. A more advanced code would use JavaFX Properties for that.
    • + *
    + * *

    Related Work

    *
      *
    • TableView SmartResize Policy
    • - *
    • CONSTRAINED_RESIZE_POLICY: This policy a) initially adjust the column widths of all columns in a way that the table fits the whole table space, b) adjusts the width of the columns right to the current column to have the whole table fit into the table space. The polciy starts with the minimum width, not with the preferred width. The policy does not support the case if the content does not fit into the table space.
    • + *
    • CONSTRAINED_RESIZE_POLICY: This policy a) initially adjust the column widths of all columns in a way that the table fits the whole table space, b) adjusts the width of the columns right to the current column to have the whole table fit into the table space. The policy starts with the minimum width, not with the preferred width. The policy does not support the case if the content does not fit into the table space.
    • *
    • UNCONSTRAINED_RESIZE_POLICY: This policy just resizes the specified column by the provided delta and shifts all other columns (to the right of the given column) further to the right (when the delta is positive) or to the left (when the delta is negative).
    • *
    • HypnosResizePolicy: Similar to CONSTRAINED_RESIZE_POLICY. However, at resize extra space is given to columns that aren't at their pref width and need that type of space (negative or positive) on a proportional basis first.
    • *
    @@ -116,19 +130,57 @@ public class SmartConstrainedResizePolicy implements Callback, Double> expansionShare = new HashMap<>(); + private Map, Double> expansionShare = new HashMap<>(); + + private List> resizableColumns; - List> resizableColumns; private TableColumn lastModifiedColumn = null; + private Map desiredColumnWidths; + private Double thresholdPercent; + private boolean firstTimeRun = true; + + public SmartConstrainedResizePolicy() { + this.desiredColumnWidths = Collections.emptyMap(); + // default is 80% + this.thresholdPercent = .8; + } + + /** + * Sets an desired column width for a set of columns. A "desired" width is a width which is wished by the user + * + * @param desiredColumnWidths + * @param thresholdPercent Value between 0 and 1 (normal percentage calculation: 1 is 100%, .8 is 80%, ...) + */ + public SmartConstrainedResizePolicy(Map desiredColumnWidths, Double thresholdPercent) { + this.desiredColumnWidths = desiredColumnWidths; + this.thresholdPercent = thresholdPercent; + } + + public Double getThreshold(TableColumn column) { + return desiredColumnWidths.getOrDefault(column, column.getPrefWidth()) * thresholdPercent; + } + @Override public Boolean call(TableView.ResizeFeatures prop) { + if (firstTimeRun) { + if (prop.getTable().getWidth() == 0.0d) { + LOGGER.debug("Table width is 0. Returning false"); + return false; + } + // case I0 + LOGGER.debug("Table is rendered the first time"); + doInitialTableRendering(prop.getTable()); + firstTimeRun = false; + return true; + } + TableColumn column = prop.getColumn(); Boolean result; if (column == null) { - // happens at initialization and at window resize + // happens at window resize LOGGER.debug("Table is fully rendered"); - result = doInitialTableRendering(prop); + result = doFullTableRendering(prop); } else { LOGGER.debug("Column width changed"); result = doColumnRearrangement(prop); @@ -137,14 +189,18 @@ public Boolean call(TableView.ResizeFeatures prop) { return result; } - private Boolean doInitialTableRendering(TableView.ResizeFeatures prop) { + private void doInitialTableRendering(TableView table) { + + } + + private Boolean doFullTableRendering(TableView.ResizeFeatures prop) { TableView table = prop.getTable(); if (table.getWidth() == 0.0d) { LOGGER.debug("Table width is 0. Returning false"); return false; } resizableColumns = table.getVisibleLeafColumns().stream() - .filter(TableColumnBase::isResizable) + .filter(TableColumn::isResizable) .collect(Collectors.toList()); if (contentFitsIntoTable(table)) { LOGGER.debug("Content fits into table initially"); @@ -184,9 +240,9 @@ private void determineExpansionShare(List> columns) expansionShare.clear(); // We need to store the initial preferred width, because "setWidth()" does not exist // There is only "setMinWidth", "setMaxWidth", and "setPrefWidth - Double allColumnsWidth = columns.stream().mapToDouble(TableColumnBase::getPrefWidth).sum(); + Double allColumnsWidth = columns.stream().mapToDouble(TableColumn::getPrefWidth).sum(); LOGGER.debug("allColumnsWidth: {}", allColumnsWidth); - for (TableColumnBase column : columns) { + for (TableColumn column : columns) { double share = column.getPrefWidth() / allColumnsWidth; LOGGER.debug("share of {}: {}", column.getText(), share); expansionShare.put(column, share); @@ -206,7 +262,7 @@ private void determineExpansionShare(List> columns) * @param delta The delta requested * @return the new size, Optional.empty() if no resize is possible */ - private Optional determineNewWidth(TableColumnBase column, Double delta) { + private Optional determineNewWidth(TableColumn column, Double delta) { // This is com.sun.javafx.scene.control.skin.Utils.boundedSize with more comments and Optionals LOGGER.trace("Column {}", column.getText()); @@ -238,7 +294,7 @@ private Optional determineNewWidth(TableColumnBase column, Double * In all cases the minimum/maximum width of each column is respected. */ private Boolean doColumnRearrangement(TableView.ResizeFeatures prop) { - TableColumn userChosenColumnToResize = prop.getColumn(); + TableColumn userChosenColumnToResize = prop.getColumn(); TableView table = prop.getTable(); Double tableWidth = table.getWidth(); @@ -278,7 +334,7 @@ private Boolean doColumnRearrangement(TableView.ResizeFeatures prop) { return newWidth.isPresent(); } - private void distributeDelta(TableView table, TableColumnBase userChosenColumnToResize, Double newWidth) { + private void distributeDelta(TableView table, TableColumn userChosenColumnToResize, Double newWidth) { userChosenColumnToResize.setPrefWidth(newWidth); List> columnsToResize = resizableColumns.stream().filter(col -> !col.equals(userChosenColumnToResize)).collect(Collectors.toList()); rearrangeColumns(table, columnsToResize); @@ -299,7 +355,7 @@ private Boolean rearrangeColumns(TableView table) { // Then, there is available non-assigned space // Distribute this space in a fair way to all resizable columns - for (TableColumnBase col : resizableColumns) { + for (TableColumn col : resizableColumns) { col.setPrefWidth(col.getMinWidth()); } LOGGER.debug("Width after setting min width: {}", getContentWidth(table)); @@ -324,7 +380,7 @@ private void rearrangeColumns(TableView table, List column : columnsToResize) { + for (TableColumn column : columnsToResize) { LOGGER.debug("Column {}", column.getText()); double share = expansionShare.get(column); // Precondition in our case: column has to have minimum width @@ -364,7 +420,7 @@ private void rearrangeColumns(TableView table, List table) { - return table.getColumns().stream().mapToDouble(TableColumnBase::getMinWidth).sum(); + return table.getColumns().stream().mapToDouble(TableColumn::getMinWidth).sum(); } /** @@ -372,6 +428,6 @@ private Double getSumMinWidthOfColumns(TableView table) { */ private Double getContentWidth(TableView table) { // The current table content width contains all visible columns: the resizable ones and the non-resizable ones - return table.getVisibleLeafColumns().stream().mapToDouble(TableColumnBase::getWidth).sum(); + return table.getVisibleLeafColumns().stream().mapToDouble(TableColumn::getWidth).sum(); } } From f75831de6c3ae091f257bf05252cc82bf3dad246 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 3 Jan 2021 20:57:52 +0100 Subject: [PATCH 20/25] Cases I0 to I3 Co-authored-by: Dominik Voigt --- .../SmartConstrainedResizePolicy.java | 116 +++++++++++++----- 1 file changed, 82 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index ecf5c75b802..3f78a592e8d 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -128,10 +128,6 @@ public class SmartConstrainedResizePolicy implements Callback, Double> expansionShare = new HashMap<>(); - private List> resizableColumns; private TableColumn lastModifiedColumn = null; @@ -157,8 +153,12 @@ public SmartConstrainedResizePolicy(Map desiredColumnWidths this.thresholdPercent = thresholdPercent; } - public Double getThreshold(TableColumn column) { - return desiredColumnWidths.getOrDefault(column, column.getPrefWidth()) * thresholdPercent; + public Double getMinWidthThreshold(TableColumn column) { + return getDesiredColumnWidth(column) * thresholdPercent; + } + + private Double getDesiredColumnWidth(TableColumn column) { + return desiredColumnWidths.getOrDefault(column, column.getPrefWidth()); } @Override @@ -168,6 +168,11 @@ public Boolean call(TableView.ResizeFeatures prop) { LOGGER.debug("Table width is 0. Returning false"); return false; } + + firstTimeGlobalVariablesInitializations(prop.getTable()); + + + // case I0 LOGGER.debug("Table is rendered the first time"); doInitialTableRendering(prop.getTable()); @@ -189,8 +194,25 @@ public Boolean call(TableView.ResizeFeatures prop) { return result; } + private void firstTimeGlobalVariablesInitializations(TableView table) { + resizableColumns = table.getVisibleLeafColumns().stream() + .filter(TableColumn::isResizable) + .collect(Collectors.toList()); + } + private void doInitialTableRendering(TableView table) { + // case I1 + resizableColumns.forEach(column -> column.setPrefWidth(getDesiredColumnWidth(column))); + if (contentFitsIntoTable(table)) { + // case I2 + Map, Double> expansionRatio = determineExpansionRatio(resizableColumns); + rearrangeColumns(table, resizableColumns, expansionRatio); + } else { + // case I3 + Map, Double> shrinkageRatio = determineShrinkageRatio(resizableColumns); + rearrangeColumns(table, resizableColumns, shrinkageRatio); + } } private Boolean doFullTableRendering(TableView.ResizeFeatures prop) { @@ -199,9 +221,6 @@ private Boolean doFullTableRendering(TableView.ResizeFeatures prop) { LOGGER.debug("Table width is 0. Returning false"); return false; } - resizableColumns = table.getVisibleLeafColumns().stream() - .filter(TableColumn::isResizable) - .collect(Collectors.toList()); if (contentFitsIntoTable(table)) { LOGGER.debug("Content fits into table initially"); LOGGER.debug("Rearranging columns"); @@ -221,35 +240,46 @@ private boolean contentFitsIntoTable(TableView table) { } /** - * Determines the share of the total width each column has. * This way, the proportions of the columns are kept during resize of the window */ - private void determineExpansionShare() { - determineExpansionShare(resizableColumns); + private Map, Double> determineExpansionRatioWithoutColumn(TableColumn excludedVisibleColumn) { + List> columns = resizableColumns.stream().filter(column -> !column.equals(excludedVisibleColumn)).collect(Collectors.toList()); + return determineExpansionRatio(columns); } /** - * This way, the proportions of the columns are kept during resize of the window + * Determines the share of the given columns + * Invariant: sum(shares) = 100% */ - private void determineExpansionShareWithoutColumn(TableColumn excludedVisibleColumn) { - List> columns = resizableColumns.stream().filter(column -> !column.equals(excludedVisibleColumn)).collect(Collectors.toList()); - determineExpansionShare(columns); - } + private Map, Double> determineExpansionRatio(List> columns) { + Map, Double> expansionRatio = new HashMap<>(); - private void determineExpansionShare(List> columns) { - expansionShare.clear(); // We need to store the initial preferred width, because "setWidth()" does not exist // There is only "setMinWidth", "setMaxWidth", and "setPrefWidth - Double allColumnsWidth = columns.stream().mapToDouble(TableColumn::getPrefWidth).sum(); + Double allColumnsWidth = columns.stream().mapToDouble(TableColumn::getWidth).sum(); LOGGER.debug("allColumnsWidth: {}", allColumnsWidth); for (TableColumn column : columns) { - double share = column.getPrefWidth() / allColumnsWidth; + double share = column.getWidth() / allColumnsWidth; LOGGER.debug("share of {}: {}", column.getText(), share); - expansionShare.put(column, share); + expansionRatio.put(column, share); + } + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Sum of shares: {}", expansionRatio.values().stream().mapToDouble(Double::doubleValue).sum()); } + return expansionRatio; + } + + /** + * Determines the shrinkage ratio. It is a uniform distribution. + */ + private Map, Double> determineShrinkageRatio(List> resizableColumns) { + Map, Double> shrinkageRatio = new HashMap<>(); + Double share = 1.0 / resizableColumns.size(); + resizableColumns.forEach(tableColumn -> shrinkageRatio.put(tableColumn, share)); if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Sum of shares: {}", expansionShare.values().stream().mapToDouble(Double::doubleValue).sum()); + LOGGER.trace("Sum of shares: {}", shrinkageRatio.values().stream().mapToDouble(Double::doubleValue).sum()); } + return shrinkageRatio; } /** @@ -271,8 +301,8 @@ private Optional determineNewWidth(TableColumn column, Double delta) { double oldWidth = column.getWidth(); double newWidth; if (delta < 0) { - double minWidth = column.getMinWidth(); - LOGGER.trace("MinWidth {}", minWidth); + double minWidth = getMinWidthThreshold(column); + LOGGER.trace("getMinWidthThreshold {}", minWidth); newWidth = Math.max(minWidth, oldWidth + delta); } else { double maxWidth = column.getMaxWidth(); @@ -315,7 +345,7 @@ private Boolean doColumnRearrangement(TableView.ResizeFeatures prop) { if (!column.equals(lastModifiedColumn)) { LOGGER.debug("Column changed"); lastModifiedColumn = column; - determineExpansionShareWithoutColumn(column); + determineExpansionRatioWithoutColumn(column); } // Content does already fit @@ -344,7 +374,7 @@ private void distributeDelta(TableView table, TableColumn userChosenColumnToR * Rearranges the widths of all columns according to their shares (proportional to their preferred widths) */ private Boolean rearrangeColumns(TableView table) { - determineExpansionShare(); + determineExpansionRatio(); Double initialContentWidth = getContentWidth(table); LOGGER.debug("initialContentWidth {}", initialContentWidth); @@ -366,13 +396,24 @@ private Boolean rearrangeColumns(TableView table) { return (initialContentWidth - getContentWidth(table)) != 0d; } - private void rearrangeColumns(TableView table, List> columnsToResize) { + /** + * Completely rearranges the given columnsToResize so that the complete table content fits into the table space (in case threshold of columns is not hit) + * + * Handles cases I2 and I3 + * + * @param table The table to handle + * @param columnsToResize The columns allowed to change the widths + * @param ratio The expansion/shrinkage ratio of the columns + */ + private void rearrangeColumns(TableView table, List> columnsToResize, Map, Double> ratio) { Double tableWidth = table.getWidth(); Double newContentWidth; + int iterations = 0; + double remainingPixels; - do - { + do { + iterations++; // in case the userChosenColumnToResize got bigger, the remaining available width will get below 0 --> other columns need to be shrunk LOGGER.debug("tableWidth {}", tableWidth); Double contentWidth = getContentWidth(table); @@ -382,7 +423,7 @@ private void rearrangeColumns(TableView table, List table, List 20d); + remainingPixels = Math.abs(tableWidth - newContentWidth); + LOGGER.debug("|tableWidth - newContentWidth| = {}", remainingPixels); + } while (remainingPixels > 20d && iterations <= 2); + + /* + + Quick hack - has to be moved elsewhere + // in case the table has less than 7 pixels, a scroll bar is there // quickfix by removing pixels double amountOfRemainingPixelsRequiredForNoScrollBar = 7.0; @@ -414,6 +460,8 @@ private void rearrangeColumns(TableView table, List Date: Sun, 3 Jan 2021 21:01:23 +0100 Subject: [PATCH 21/25] First proposal of case names --- .../SmartConstrainedResizePolicy.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 3f78a592e8d..4f9a05b276d 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -63,38 +63,38 @@ *
  • Desired width is set to a reasonable value. The desired width is a "globally" preferred width.
  • * * - *

    Resizing of column

    + *

    RC0 - Resizing of column

    * *
      - *
    • If enlarged + *
    • RCE0 - If enlarged *
        - *
      • Content has fit into table. Then, content does not fit into table (because of delta). Column gets enlarged by the delta. Other columns should not below a certain "reasonable" size ("threshold"). All columns are shrunk (all equally) until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
      • - *
      • Content has not fit into table. Then, content still does not fit into table (because of delta). Column gets enlarged by the delta. Other columns are not changed. Scrollbar gets wider.
      • + *
      • RCE1 - Content has fit into table. Then, content does not fit into table (because of delta). Column gets enlarged by the delta. Other columns should not below a certain "reasonable" size ("threshold"). All columns are shrunk (all equally) until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
      • + *
      • RCE2 - Content has not fit into table. Then, content still does not fit into table (because of delta). Column gets enlarged by the delta. Other columns are not changed. Scrollbar gets wider.
      • *
      *
    • - *
    • If shrunk + *
    • RCS0 - If shrunk *
        - *
      • Content has fit into table. Then, content still fits into table (because of delta). Column must not shrink below minimum width. If minimum is reached, nothing happens. Remaining delta is distributed among other columns. No scrollbar present.
      • - *
      • Content has not fit into table. If the delta is applied, the content fits into the table. Delta is applied to the column. Remaining delta splits up of delta-in-bounds and delta-out-of-bounds. Delta-in-bounds is distributed among other columns. Scrollbar disappears.
      • - *
      • Content has not fit into table. If the delta is applied, the content still does not fit into table. Column is shrunk respecting the delta. Other columns are resized. Scrollbar shrinks.
      • + *
      • RCS1 - Content has fit into table. Then, content still fits into table (because of delta). Column must not shrink below minimum width. If minimum is reached, nothing happens. Remaining delta is distributed among other columns. No scrollbar present.
      • + *
      • RCS2 - Content has not fit into table. If the delta is applied, the content fits into the table. Delta is applied to the column. Remaining delta splits up of delta-in-bounds and delta-out-of-bounds. Delta-in-bounds is distributed among other columns. Scrollbar disappears.
      • + *
      • RCS3 - Content has not fit into table. If the delta is applied, the content still does not fit into table. Column is shrunk respecting the delta. Other columns are resized. Scrollbar shrinks.
      • *
      *
    • *
    * - *

    Resizing of window

    + *

    RW0 - Resizing of window

    * *
      - *
    • If enlarged + *
    • RWE0 - If enlarged *
        - *
      • Content has fit into table. Then, content still has to fit into table. Thus, enlarge all columns respecting the ratio (the content is enlarged proportional to actual width). Scrollbar not present.
      • - *
      • Content has not fit into table. Case: Content still not fits into table. No resize action. Scrollbar shrinks.
      • - *
      • Content has not fit into table. Case: Content fits into table. Calculate the delta and distribute across all resizable columns using the ratio. Scrollbar not present any more.
      • + *
      • RWE1 - Content has fit into table. Then, content still has to fit into table. Thus, enlarge all columns respecting the ratio (the content is enlarged proportional to actual width). Scrollbar not present.
      • + *
      • RWE2 - Content has not fit into table. Case: Content still not fits into table. No resize action. Scrollbar shrinks.
      • + *
      • RWE3 - Content has not fit into table. Case: Content fits into table. Calculate the delta and distribute across all resizable columns using the ratio. Scrollbar not present any more.
      • *
      *
    • - *
    • If shrunk + *
    • RWS0 - If shrunk *
        - *
      • Content has fit into table. Content does not fit into table anymore (because of shrinkage). Delta is absorbed by columns until threshold is reached. If not complete delta can be absorbed, scrollbar appears.
      • - *
      • Content has not fit into table. Content still does not fit into table (because of shrinkage). Scrollbar enlarges.
      • + *
      • RWS1 - Content has fit into table. Content does not fit into table anymore (because of shrinkage). Delta is absorbed by columns until threshold is reached. If not complete delta can be absorbed, scrollbar appears.
      • + *
      • RWS2 - Content has not fit into table. Content still does not fit into table (because of shrinkage). Scrollbar enlarges.
      • *
      *
    • *
    From 496e505831f8023af4b7d023161e6ccdaf12b37c Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Wed, 6 Jan 2021 16:33:45 +0100 Subject: [PATCH 22/25] WIP: Reimplement logic according to comments Co-authored-by: Dominik Voigt --- .../SmartConstrainedResizePolicy.java | 213 +++++++----------- src/main/resources/log4j2.xml | 3 + 2 files changed, 90 insertions(+), 126 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 4f9a05b276d..6945d24b367 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -16,7 +16,6 @@ import org.slf4j.LoggerFactory; /** - * * This resize policy supports following properties – preferably user-configurable: * *
      @@ -25,7 +24,7 @@ *
    • Honoring non-resizable columns (i.e., that a column should not be scaled)
    • *
    • Desired width derivation (e.g., 80% of the desired width) is a point from which on the column should not be shrunk any more. This is called the threshold.
    • *
    - * + *

    * This resize policy supports following properties (non user-configurable): * *

      @@ -53,7 +52,7 @@ * *
    • The ratio used for enlargement of columns respects the current column width. The ratio for shrinkage is constant for all columns.
    • *
    - * + *

    * The implementation is driven by following factors: * *

      @@ -69,14 +68,14 @@ *
    • RCE0 - If enlarged *
        *
      • RCE1 - Content has fit into table. Then, content does not fit into table (because of delta). Column gets enlarged by the delta. Other columns should not below a certain "reasonable" size ("threshold"). All columns are shrunk (all equally) until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
      • - *
      • RCE2 - Content has not fit into table. Then, content still does not fit into table (because of delta). Column gets enlarged by the delta. Other columns are not changed. Scrollbar gets wider.
      • + *
      • RCE2 - Content has not fit into table. Then, content still does not fit into table (because of delta). Column gets enlarged by the delta. Other columns are not changed (consistency to RCS3). Scrollbar gets wider.
      • *
      *
    • *
    • RCS0 - If shrunk *
        *
      • RCS1 - Content has fit into table. Then, content still fits into table (because of delta). Column must not shrink below minimum width. If minimum is reached, nothing happens. Remaining delta is distributed among other columns. No scrollbar present.
      • *
      • RCS2 - Content has not fit into table. If the delta is applied, the content fits into the table. Delta is applied to the column. Remaining delta splits up of delta-in-bounds and delta-out-of-bounds. Delta-in-bounds is distributed among other columns. Scrollbar disappears.
      • - *
      • RCS3 - Content has not fit into table. If the delta is applied, the content still does not fit into table. Column is shrunk respecting the delta. Other columns are resized. Scrollbar shrinks.
      • + *
      • RCS3 - Content has not fit into table. If the delta is applied, the content still does not fit into table. Column is shrunk respecting the delta. Other columns are not changed (consistency to RCE2). Scrollbar shrinks.
      • *
      *
    • *
    @@ -100,12 +99,12 @@ * * *

    I0 - Initial table rendering

    - * + *

    * Decision to take: a) Use last setting or b) rerender with desired widths. We opt for b), because we assume that user-configuration of desired widths is easy. * *

      *
    • I1 - Initially, all columns takes the desired width.
    • - *
    • I2 - If content fits into table, distribute remaining delta to table space according to the shares. No scollbar.
    • + *
    • I2 - If content fits into table, distribute remaining delta to table space according to the shares. No scrollbar.
    • *
    • I3 - If content does not fit into table, all columns are shrunk (all equally) until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
    • *
    * @@ -146,7 +145,7 @@ public SmartConstrainedResizePolicy() { * Sets an desired column width for a set of columns. A "desired" width is a width which is wished by the user * * @param desiredColumnWidths - * @param thresholdPercent Value between 0 and 1 (normal percentage calculation: 1 is 100%, .8 is 80%, ...) + * @param thresholdPercent Value between 0 and 1 (normal percentage calculation: 1 is 100%, .8 is 80%, ...) */ public SmartConstrainedResizePolicy(Map desiredColumnWidths, Double thresholdPercent) { this.desiredColumnWidths = desiredColumnWidths; @@ -161,6 +160,9 @@ private Double getDesiredColumnWidth(TableColumn column) { return desiredColumnWidths.getOrDefault(column, column.getPrefWidth()); } + /** + * @return false if surely no changes happened, true if some change happened + */ @Override public Boolean call(TableView.ResizeFeatures prop) { if (firstTimeRun) { @@ -171,24 +173,25 @@ public Boolean call(TableView.ResizeFeatures prop) { firstTimeGlobalVariablesInitializations(prop.getTable()); - - // case I0 LOGGER.debug("Table is rendered the first time"); doInitialTableRendering(prop.getTable()); firstTimeRun = false; + LOGGER.debug("First time rendering completed."); return true; } + LOGGER.debug("RC0, RW0"); + TableColumn column = prop.getColumn(); Boolean result; if (column == null) { // happens at window resize - LOGGER.debug("Table is fully rendered"); - result = doFullTableRendering(prop); + LOGGER.debug("RW0 - Table is fully rendered"); + result = doFullTableRendering(prop.getTable()); } else { - LOGGER.debug("Column width changed"); - result = doColumnRearrangement(prop); + LOGGER.debug("RC0 - Column width changed"); + result = doColumnChange(prop.getTable(), prop.getColumn(), prop.getDelta()); } LOGGER.debug("Result: {}", result); return result; @@ -200,35 +203,71 @@ private void firstTimeGlobalVariablesInitializations(TableView table) { .collect(Collectors.toList()); } + /** + * Case I0 + */ private void doInitialTableRendering(TableView table) { - // case I1 + LOGGER.debug("I1"); resizableColumns.forEach(column -> column.setPrefWidth(getDesiredColumnWidth(column))); + LOGGER.debug("I2, I3"); + doFullTableRendering(table); + } + + /** + * Cases I0 and RW0 + */ + private Boolean doFullTableRendering(TableView table) { + if (table.getWidth() == 0.0d) { + LOGGER.error("Table width is 0. Returning false"); + return false; + } if (contentFitsIntoTable(table)) { - // case I2 + LOGGER.debug("I2, RWE1"); Map, Double> expansionRatio = determineExpansionRatio(resizableColumns); - rearrangeColumns(table, resizableColumns, expansionRatio); + return rearrangeColumns(table, resizableColumns, expansionRatio); } else { - // case I3 + // TODO: Think about RWE2 + LOGGER.debug("I3, RWE2, RWE3, RWS1, RWS2"); Map, Double> shrinkageRatio = determineShrinkageRatio(resizableColumns); - rearrangeColumns(table, resizableColumns, shrinkageRatio); + return rearrangeColumns(table, resizableColumns, shrinkageRatio); } } - private Boolean doFullTableRendering(TableView.ResizeFeatures prop) { - TableView table = prop.getTable(); + /** + * case RC0 + */ + private boolean doColumnChange(TableView table, TableColumn userChangedColumn, Double sizeChange) { if (table.getWidth() == 0.0d) { - LOGGER.debug("Table width is 0. Returning false"); + LOGGER.error("Table width is 0. Returning false"); return false; } - if (contentFitsIntoTable(table)) { - LOGGER.debug("Content fits into table initially"); - LOGGER.debug("Rearranging columns"); - return rearrangeColumns(table); - } else { - LOGGER.debug("Content too wide for displayed table. Using default alignment"); - } - return false; + + return determineNewWidth(userChangedColumn, sizeChange).map(newWidth -> { + boolean contentHasFitIntoTableBefore = contentFitsIntoTable(table); + userChangedColumn.setPrefWidth(newWidth); + List> otherResizableColumns = resizableColumns.stream().filter(column -> !column.equals(userChangedColumn)).collect(Collectors.toList()); + if (contentHasFitIntoTableBefore) { + LOGGER.debug("RCE1, RCS1"); + Map, Double> shrinkageRatio = determineShrinkageRatio(otherResizableColumns); + rearrangeColumns(table, otherResizableColumns, shrinkageRatio); + } else { + if (sizeChange >= 0) { + LOGGER.debug("RCE2"); + // no more action, because other columns are not changed. + } else { + if (contentFitsIntoTable(table)) { + LOGGER.debug("RCS2"); + Map, Double> expansionRatio = determineExpansionRatio(otherResizableColumns); + rearrangeColumns(table, otherResizableColumns, expansionRatio); + } else { + LOGGER.debug("RCS3"); + // no more action, because other columns are not changed. + } + } + } + return true; + }).orElse(false); } private boolean contentFitsIntoTable(TableView table) { @@ -272,7 +311,7 @@ private boolean contentFitsIntoTable(TableView table) { /** * Determines the shrinkage ratio. It is a uniform distribution. */ - private Map, Double> determineShrinkageRatio(List> resizableColumns) { + private Map, Double> determineShrinkageRatio(List> resizableColumns) { Map, Double> shrinkageRatio = new HashMap<>(); Double share = 1.0 / resizableColumns.size(); resizableColumns.forEach(tableColumn -> shrinkageRatio.put(tableColumn, share)); @@ -317,102 +356,25 @@ private Optional determineNewWidth(TableColumn column, Double delta) { return Optional.of(newWidth); } - /** - * Resizes the table based on the content. The main driver is that if the content might fit into the table without horizontal scroll bar. - * In case the content fitted before the resize and will fit afterwards, the delta is distributed among the remaining columns - instead of just moving the columns right of the current column. - * In case the content does not fit anymore, a horizontal scroll bar is shown. - * In all cases the minimum/maximum width of each column is respected. - */ - private Boolean doColumnRearrangement(TableView.ResizeFeatures prop) { - TableColumn userChosenColumnToResize = prop.getColumn(); - - TableView table = prop.getTable(); - Double tableWidth = table.getWidth(); - Double currentTableContentWidth = getContentWidth(table); - Double delta = prop.getDelta(); - boolean columnsCanFitTable = currentTableContentWidth + delta < tableWidth; - LOGGER.debug("currentTableContentWidth {} + delta {} < tableWidth {} = {}", currentTableContentWidth, delta, tableWidth, columnsCanFitTable); - if (columnsCanFitTable) { - LOGGER.debug("User changed column size in a way that window can contain all the columns"); - Optional newWidthOptional; - if (currentTableContentWidth >= tableWidth) { - LOGGER.debug("Before, the content did not fit. Now it fits. Rearrange everything."); - return rearrangeColumns(table); - } - LOGGER.debug("Everything already fit. We distribute the delta now."); - - TableColumn column = prop.getColumn(); - if (!column.equals(lastModifiedColumn)) { - LOGGER.debug("Column changed"); - lastModifiedColumn = column; - determineExpansionRatioWithoutColumn(column); - } - - // Content does already fit - // We "just" need to readjust the column widths - newWidthOptional = determineNewWidth(userChosenColumnToResize, delta); - newWidthOptional.ifPresent(newWidth -> { - distributeDelta(table, userChosenColumnToResize, newWidth); - }); - return newWidthOptional.isPresent(); - } - - LOGGER.debug("Window smaller than content"); - - Optional newWidth = determineNewWidth(userChosenColumnToResize, delta); - newWidth.ifPresent(userChosenColumnToResize::setPrefWidth); - return newWidth.isPresent(); - } - - private void distributeDelta(TableView table, TableColumn userChosenColumnToResize, Double newWidth) { - userChosenColumnToResize.setPrefWidth(newWidth); - List> columnsToResize = resizableColumns.stream().filter(col -> !col.equals(userChosenColumnToResize)).collect(Collectors.toList()); - rearrangeColumns(table, columnsToResize); - } - - /** - * Rearranges the widths of all columns according to their shares (proportional to their preferred widths) - */ - private Boolean rearrangeColumns(TableView table) { - determineExpansionRatio(); - - Double initialContentWidth = getContentWidth(table); - LOGGER.debug("initialContentWidth {}", initialContentWidth); - - // Implementation idea: - // Each column has to have at least the minimum width - // First, set the minimum width of all columns - // Then, there is available non-assigned space - // Distribute this space in a fair way to all resizable columns - - for (TableColumn col : resizableColumns) { - col.setPrefWidth(col.getMinWidth()); - } - LOGGER.debug("Width after setting min width: {}", getContentWidth(table)); - - rearrangeColumns(table, resizableColumns); - LOGGER.debug("Width after rearranging columns: {}", getContentWidth(table)); - - return (initialContentWidth - getContentWidth(table)) != 0d; - } - /** * Completely rearranges the given columnsToResize so that the complete table content fits into the table space (in case threshold of columns is not hit) + *

    + * Handles cases I2, I3, RWE1 to RWE3, and RWS1 and RWS2 * - * Handles cases I2 and I3 - * - * @param table The table to handle + * @param table The table to handle * @param columnsToResize The columns allowed to change the widths - * @param ratio The expansion/shrinkage ratio of the columns + * @param ratio The expansion/shrinkage ratio of the columns */ - private void rearrangeColumns(TableView table, List> columnsToResize, Map, Double> ratio) { + private boolean rearrangeColumns(TableView table, List> columnsToResize, Map, Double> ratio) { Double tableWidth = table.getWidth(); Double newContentWidth; + boolean aWidthChanged = false; int iterations = 0; double remainingPixels; - do { + do + { iterations++; // in case the userChosenColumnToResize got bigger, the remaining available width will get below 0 --> other columns need to be shrunk LOGGER.debug("tableWidth {}", tableWidth); @@ -427,10 +389,13 @@ private void rearrangeColumns(TableView table, List newWidth = determineNewWidth(column, delta); + if (newWidth.isPresent()) { + // in case we can do something, do it + // otherwise, the next loop iteration will distribute it + aWidthChanged = true; + column.setPrefWidth(newWidth.get()); + } } newContentWidth = getContentWidth(table); LOGGER.debug("newContentWidth {}", newContentWidth); @@ -462,13 +427,9 @@ private void rearrangeColumns(TableView table, List table) { - return table.getColumns().stream().mapToDouble(TableColumn::getMinWidth).sum(); + LOGGER.debug("aWidthChanged is {}", aWidthChanged); + return aWidthChanged; } /** diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 98202e744f3..940fdc6391b 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -18,6 +18,9 @@ + + + From caf1e239eac06ce824567b23f116fee6a3f5897c Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Thu, 7 Jan 2021 16:29:29 +0100 Subject: [PATCH 23/25] WIP Co-authored-by: Dominik Voigt --- .../SmartConstrainedResizePolicy.java | 78 ++++++++++++------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 6945d24b367..56f153a3309 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -62,22 +62,14 @@ *

  • Desired width is set to a reasonable value. The desired width is a "globally" preferred width.
  • * * - *

    RC0 - Resizing of column

    + *

    I0 - Initial table rendering

    + *

    + * Decision to take: a) Use last setting or b) rerender with desired widths. We opt for b), because we assume that user-configuration of desired widths is easy. This leads to I3 being inconsistent to RWS2 (there is no previous width in I3) * *

      - *
    • RCE0 - If enlarged - *
        - *
      • RCE1 - Content has fit into table. Then, content does not fit into table (because of delta). Column gets enlarged by the delta. Other columns should not below a certain "reasonable" size ("threshold"). All columns are shrunk (all equally) until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
      • - *
      • RCE2 - Content has not fit into table. Then, content still does not fit into table (because of delta). Column gets enlarged by the delta. Other columns are not changed (consistency to RCS3). Scrollbar gets wider.
      • - *
      - *
    • - *
    • RCS0 - If shrunk - *
        - *
      • RCS1 - Content has fit into table. Then, content still fits into table (because of delta). Column must not shrink below minimum width. If minimum is reached, nothing happens. Remaining delta is distributed among other columns. No scrollbar present.
      • - *
      • RCS2 - Content has not fit into table. If the delta is applied, the content fits into the table. Delta is applied to the column. Remaining delta splits up of delta-in-bounds and delta-out-of-bounds. Delta-in-bounds is distributed among other columns. Scrollbar disappears.
      • - *
      • RCS3 - Content has not fit into table. If the delta is applied, the content still does not fit into table. Column is shrunk respecting the delta. Other columns are not changed (consistency to RCE2). Scrollbar shrinks.
      • - *
      - *
    • + *
    • I1 - Initially, all columns takes the desired width.
    • + *
    • I2 - If content fits into table, distribute remaining delta to table space according to the shares. No scrollbar.
    • + *
    • I3 - If content does not fit into table, all columns are shrunk (all equally) until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
    • *
    * *

    RW0 - Resizing of window

    @@ -98,14 +90,22 @@ * * * - *

    I0 - Initial table rendering

    - *

    - * Decision to take: a) Use last setting or b) rerender with desired widths. We opt for b), because we assume that user-configuration of desired widths is easy. + *

    RC0 - Resizing of column

    * *
      - *
    • I1 - Initially, all columns takes the desired width.
    • - *
    • I2 - If content fits into table, distribute remaining delta to table space according to the shares. No scrollbar.
    • - *
    • I3 - If content does not fit into table, all columns are shrunk (all equally) until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
    • + *
    • RCE0 - If enlarged + *
        + *
      • RCE1 - Content has fit into table. Then, content does not fit into table (because of delta). Column gets enlarged by the delta. Other columns should not below a certain "reasonable" size ("threshold"). All columns are shrunk (all equally) until all columns hit the threshold. In case no column can shrink any more (they are smaller or equal the threshold), they are not shrunk. Thus, the table gets wider than the table space. This leads to a scrollbar.
      • + *
      • RCE2 - Content has not fit into table. Then, content still does not fit into table (because of delta). Column gets enlarged by the delta. Other columns are not changed (consistency to RCS3). Scrollbar gets wider.
      • + *
      + *
    • + *
    • RCS0 - If shrunk + *
        + *
      • RCS1 - Content has fit into table. Then, content still fits into table (because of delta). Column must not shrink below minimum width. If minimum is reached, nothing happens. Remaining delta is distributed among other columns. No scrollbar present.
      • + *
      • RCS2 - Content has not fit into table. If the delta is applied, the content fits into the table. Delta is applied to the column. Remaining delta splits up of delta-in-bounds and delta-out-of-bounds. Delta-in-bounds is distributed among other columns. Scrollbar disappears.
      • + *
      • RCS3 - Content has not fit into table. If the delta is applied, the content still does not fit into table. Column is shrunk respecting the delta. Other columns are not changed (consistency to RCE2). Scrollbar shrinks.
      • + *
      + *
    • *
    * *

    Notes

    @@ -133,6 +133,10 @@ public class SmartConstrainedResizePolicy implements Callback desiredColumnWidths; private Double thresholdPercent; + + // Required for RW0 + private Double previousTableWidth; + private boolean firstTimeRun = true; public SmartConstrainedResizePolicy() { @@ -173,11 +177,12 @@ public Boolean call(TableView.ResizeFeatures prop) { firstTimeGlobalVariablesInitializations(prop.getTable()); - // case I0 - LOGGER.debug("Table is rendered the first time"); + LOGGER.debug("I0"); doInitialTableRendering(prop.getTable()); firstTimeRun = false; LOGGER.debug("First time rendering completed."); + previousTableWidth = prop.getTable().getWidth(); + LOGGER.debug("Storing current table width {} as \"previous table width\"", previousTableWidth); return true; } @@ -194,6 +199,8 @@ public Boolean call(TableView.ResizeFeatures prop) { result = doColumnChange(prop.getTable(), prop.getColumn(), prop.getDelta()); } LOGGER.debug("Result: {}", result); + previousTableWidth = prop.getTable().getWidth(); + LOGGER.debug("Storing current table width {} as \"previous table width\"", previousTableWidth); return result; } @@ -207,15 +214,26 @@ private void firstTimeGlobalVariablesInitializations(TableView table) { * Case I0 */ private void doInitialTableRendering(TableView table) { + if (table.getWidth() == 0.0d) { + LOGGER.error("Table width is 0. Returning false"); + } + LOGGER.debug("I1"); resizableColumns.forEach(column -> column.setPrefWidth(getDesiredColumnWidth(column))); - LOGGER.debug("I2, I3"); - doFullTableRendering(table); + if (contentFitsIntoTable(table)) { + LOGGER.debug("I2"); + Map, Double> expansionRatio = determineExpansionRatio(resizableColumns); + rearrangeColumns(table, resizableColumns, expansionRatio); + } else { + LOGGER.debug("I3"); + Map, Double> shrinkageRatio = determineShrinkageRatio(resizableColumns); + rearrangeColumns(table, resizableColumns, shrinkageRatio); + } } /** - * Cases I0 and RW0 + * Case RW0 */ private Boolean doFullTableRendering(TableView table) { if (table.getWidth() == 0.0d) { @@ -223,12 +241,16 @@ private Boolean doFullTableRendering(TableView table) { return false; } if (contentFitsIntoTable(table)) { - LOGGER.debug("I2, RWE1"); + LOGGER.debug("RWE1"); Map, Double> expansionRatio = determineExpansionRatio(resizableColumns); return rearrangeColumns(table, resizableColumns, expansionRatio); } else { - // TODO: Think about RWE2 - LOGGER.debug("I3, RWE2, RWE3, RWS1, RWS2"); + LOGGER.debug("RWE2, RWE3, RWS1, RWS2"); + boolean contentHasFitIntoTable = getContentWidth(table) <= previousTableWidth; + if (!contentHasFitIntoTable) { + LOGGER.debug("RWS2"); + return false; + } Map, Double> shrinkageRatio = determineShrinkageRatio(resizableColumns); return rearrangeColumns(table, resizableColumns, shrinkageRatio); } From 274e10986ee03fc221cae5fcb871776b913b18ec Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 10 Jan 2021 14:11:46 +0100 Subject: [PATCH 24/25] Resizing of colomms works Co-authored-by: Dominik Voigt --- .../SmartConstrainedResizePolicy.java | 90 +++++++++++-------- src/main/resources/log4j2.xml | 4 +- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 56f153a3309..84b653d6e5d 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -2,7 +2,6 @@ import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -127,11 +126,15 @@ public class SmartConstrainedResizePolicy implements Callback> resizableColumns; private TableColumn lastModifiedColumn = null; private Map desiredColumnWidths; + private Double thresholdPercent; // Required for RW0 @@ -140,7 +143,7 @@ public class SmartConstrainedResizePolicy implements Callback(); // default is 80% this.thresholdPercent = .8; } @@ -160,8 +163,14 @@ public Double getMinWidthThreshold(TableColumn column) { return getDesiredColumnWidth(column) * thresholdPercent; } + /** + * SIDE EFFECT: Stores computed desired width if not present in HashMap. This leads to a constant desired width. + */ private Double getDesiredColumnWidth(TableColumn column) { - return desiredColumnWidths.getOrDefault(column, column.getPrefWidth()); + desiredColumnWidths.putIfAbsent(column, column.getPrefWidth()); + Double result = desiredColumnWidths.get(column); + LOGGER.trace("Desired column width for {}: {}", column.getText(), result); + return result; } /** @@ -169,19 +178,20 @@ private Double getDesiredColumnWidth(TableColumn column) { */ @Override public Boolean call(TableView.ResizeFeatures prop) { + TableView table = prop.getTable(); if (firstTimeRun) { - if (prop.getTable().getWidth() == 0.0d) { + if (table.getWidth() == 0.0d) { LOGGER.debug("Table width is 0. Returning false"); return false; } - firstTimeGlobalVariablesInitializations(prop.getTable()); + firstTimeGlobalVariablesInitializations(table); LOGGER.debug("I0"); - doInitialTableRendering(prop.getTable()); + doInitialTableRendering(table); firstTimeRun = false; LOGGER.debug("First time rendering completed."); - previousTableWidth = prop.getTable().getWidth(); + previousTableWidth = table.getWidth(); LOGGER.debug("Storing current table width {} as \"previous table width\"", previousTableWidth); return true; } @@ -193,13 +203,17 @@ public Boolean call(TableView.ResizeFeatures prop) { if (column == null) { // happens at window resize LOGGER.debug("RW0 - Table is fully rendered"); - result = doFullTableRendering(prop.getTable()); + if (previousTableWidth == table.getWidth()) { + LOGGER.debug("Table has same size as in last run. Nothing to do"); + return false; + } + result = doFullTableRendering(table); } else { LOGGER.debug("RC0 - Column width changed"); - result = doColumnChange(prop.getTable(), prop.getColumn(), prop.getDelta()); + result = doColumnChange(table, prop.getColumn(), prop.getDelta()); } LOGGER.debug("Result: {}", result); - previousTableWidth = prop.getTable().getWidth(); + previousTableWidth = table.getWidth(); LOGGER.debug("Storing current table width {} as \"previous table width\"", previousTableWidth); return result; } @@ -260,14 +274,18 @@ private Boolean doFullTableRendering(TableView table) { * case RC0 */ private boolean doColumnChange(TableView table, TableColumn userChangedColumn, Double sizeChange) { + LOGGER.debug("Start RC0 with column {} sizeChange {}", userChangedColumn.getText(), sizeChange); if (table.getWidth() == 0.0d) { LOGGER.error("Table width is 0. Returning false"); return false; } return determineNewWidth(userChangedColumn, sizeChange).map(newWidth -> { + LOGGER.debug("Checking if content has fit into table before"); boolean contentHasFitIntoTableBefore = contentFitsIntoTable(table); + LOGGER.debug("Result: contentHasFitIntoTableBefore: {}", contentHasFitIntoTableBefore); userChangedColumn.setPrefWidth(newWidth); + LOGGER.trace("New width set"); List> otherResizableColumns = resizableColumns.stream().filter(column -> !column.equals(userChangedColumn)).collect(Collectors.toList()); if (contentHasFitIntoTableBefore) { LOGGER.debug("RCE1, RCS1"); @@ -357,6 +375,7 @@ private Optional determineNewWidth(TableColumn column, Double delta) { // This is com.sun.javafx.scene.control.skin.Utils.boundedSize with more comments and Optionals LOGGER.trace("Column {}", column.getText()); + LOGGER.trace("Requested delta {}", delta); // Calculate newWidth based on delta and constraint of the column double oldWidth = column.getWidth(); @@ -365,13 +384,16 @@ private Optional determineNewWidth(TableColumn column, Double delta) { double minWidth = getMinWidthThreshold(column); LOGGER.trace("getMinWidthThreshold {}", minWidth); newWidth = Math.max(minWidth, oldWidth + delta); + LOGGER.trace("newWidth {} = Math.max(minWidth {}, oldWidth {} + delta {})", newWidth, minWidth, oldWidth, delta); } else { double maxWidth = column.getMaxWidth(); LOGGER.trace("MaxWidth {}", maxWidth); newWidth = Math.min(maxWidth, oldWidth + delta); + LOGGER.trace("newWidth {} = Math.min(maxWidth {}, oldWidth {} + delta {})", newWidth, maxWidth, oldWidth, delta); } - newWidth = Math.floor(newWidth) - 2d; - LOGGER.trace("Size: {} -> {}", oldWidth, newWidth); + LOGGER.trace("Truncating width"); + newWidth = Math.floor(newWidth * 1.0E10) / 1.0E10; + LOGGER.trace("Size proposal: {} -> {}", oldWidth, newWidth); if (oldWidth == newWidth) { return Optional.empty(); } @@ -392,6 +414,15 @@ private boolean rearrangeColumns(TableView table, List 70 pixels should be shrunk + // - Columns should be shrunk + // - Columns cannot be shrunk because of threshold + // Therefore, we introduce an iterations counter to have a safety addition to the termination condition. int iterations = 0; double remainingPixels; @@ -419,37 +450,22 @@ private boolean rearrangeColumns(TableView table, List 20d && iterations <= 2); - - /* - - Quick hack - has to be moved elsewhere - - // in case the table has less than 7 pixels, a scroll bar is there - // quickfix by removing pixels - double amountOfRemainingPixelsRequiredForNoScrollBar = 7.0; - if (remainingPixels < amountOfRemainingPixelsRequiredForNoScrollBar) { - double pixelsToRemove = amountOfRemainingPixelsRequiredForNoScrollBar - remainingPixels; - Iterator> iterator = columnsToResize.iterator(); - while (pixelsToRemove > 0) { - if (!iterator.hasNext()) { - iterator = columnsToResize.iterator(); - } - TableColumn column = iterator.next(); - Optional aDouble = determineNewWidth(column, -1.0); - if (aDouble.isPresent()) { - column.setPrefWidth(aDouble.get()); - pixelsToRemove--; - } - } + } while (remainingPixels > EPSILON_MARGIN && iterations <= 2); + + // Special case if due to rounding errors contentWidth is still larger than tableWidth + // E.g., contentWidth - tableWidth = 0.0000000000002 + if (remainingPixels < 1) { + columnsToResize.stream().filter(column -> column.getWidth() > column.getMinWidth() + 1).findFirst(); + TableColumn columnToAbsorbEpsilon = columnsToResize.get(0); + columnToAbsorbEpsilon.setPrefWidth(columnToAbsorbEpsilon.getWidth() - remainingPixels); } - */ - LOGGER.debug("aWidthChanged is {}", aWidthChanged); return aWidthChanged; } diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 940fdc6391b..b2bce92f4d2 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -2,12 +2,12 @@ - + - + From 910c592b2ca01e3861667afca7e1022f873bc231 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 10 Jan 2021 14:41:34 +0100 Subject: [PATCH 25/25] Use EPSILON_MARGIN also for "content has fit into table before" Co-authored-by: Dominik Voigt --- .../org/jabref/gui/maintable/SmartConstrainedResizePolicy.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 84b653d6e5d..7fe1e2b3a78 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -260,7 +260,8 @@ private Boolean doFullTableRendering(TableView table) { return rearrangeColumns(table, resizableColumns, expansionRatio); } else { LOGGER.debug("RWE2, RWE3, RWS1, RWS2"); - boolean contentHasFitIntoTable = getContentWidth(table) <= previousTableWidth; + boolean contentHasFitIntoTable = getContentWidth(table) - EPSILON_MARGIN <= previousTableWidth; + LOGGER.debug("contentHasFitIntoTable {} = getContentWidth(table) {} <= previousTableWidth {}", contentHasFitIntoTable, getContentWidth(table), previousTableWidth); if (!contentHasFitIntoTable) { LOGGER.debug("RWS2"); return false;