Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement better scaling of main table showing entries #7181

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a1a5ccc
Implement better scaling of main table showing entries
koppor Dec 12, 2020
e91bb8d
Fix number
koppor Dec 14, 2020
8f522df
Remove resizeColumnsToFit
koppor Dec 14, 2020
1acd12e
Fix checkstyle
koppor Dec 14, 2020
c8caa13
Merge branch 'master' into fix-967
koppor Dec 17, 2020
f5e2d9f
Add smart resize if content fits into table width
koppor Dec 17, 2020
bcea66c
Address some comments
koppor Dec 18, 2020
6da49b2
WIP
koppor Dec 21, 2020
dfac705
Speedup processResources
koppor Dec 21, 2020
057ae9f
Merge branch 'speedup-process-resources' into fix-967
koppor Dec 21, 2020
8834fb6
Try path without ./ at the beginning
koppor Dec 21, 2020
5b6f0d8
Output java error on console, too
koppor Dec 21, 2020
fd80c30
Use LOGGER instead of System.err
koppor Dec 21, 2020
c64b5e3
Merge branch 'fix-output-with-wrong-jdk-version' into fix-967
koppor Dec 21, 2020
9f56fd5
Merge branch 'speedup-process-resources' into fix-967
koppor Dec 21, 2020
d15e90f
Fix scroll bar still present
koppor Dec 21, 2020
7de21ad
MinWidth to 80
koppor Dec 21, 2020
8c7cd7a
Merge branch 'master' into fix-967
koppor Dec 21, 2020
f7186f3
Merge branch 'fix-967' of github.com:JabRef/jabref into fix-967
koppor Dec 21, 2020
80557f2
WIP (does not work)
koppor Dec 22, 2020
043a0cc
Merge remote-tracking branch 'origin/master' into fix-967
koppor Dec 29, 2020
c3b02e3
Fix ordering in CHANGELOG.md
koppor Dec 29, 2020
fd118a6
WIP
koppor Dec 29, 2020
f0788f6
WIP - "User changed column size in a way that window can contain all …
koppor Dec 29, 2020
a1633a1
Add documentation on implementation idea
koppor Jan 2, 2021
73d5448
Refine description (and change TableColumnBase to TableColumn)
koppor Jan 3, 2021
f75831d
Cases I0 to I3
koppor Jan 3, 2021
c417d7d
First proposal of case names
koppor Jan 3, 2021
496e505
WIP: Reimplement logic according to comments
koppor Jan 6, 2021
466731f
Merge remote-tracking branch 'origin/master' into fix-967
koppor Jan 7, 2021
caf1e23
WIP
koppor Jan 7, 2021
63af8fd
Merge remote-tracking branch 'origin/master' into fix-967
koppor Jan 10, 2021
274e109
Resizing of colomms works
koppor Jan 10, 2021
910c592
Use EPSILON_MARGIN also for "content has fit into table before"
koppor Jan 10, 2021
513c68c
Merge branch 'master' into fix-967
koppor Jan 21, 2021
286e7f9
Merge remote-tracking branch 'upstream/master' into fix-967
calixtus Feb 6, 2021
caf7232
Merge branch 'main' into fix-967
koppor Oct 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Changelog

All notable changes to this project will be documented in this file.
Expand Down Expand Up @@ -47,8 +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 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 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)

### Fixed

Expand Down
4 changes: 2 additions & 2 deletions docs/adr/0016-mutable-preferences-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 10 additions & 1 deletion src/main/java/org/jabref/gui/maintable/ColumnPreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<MainTableColumnModel> columns;

private final List<MainTableColumnModel> columnSortOrder;

public ColumnPreferences(List<MainTableColumnModel> columns, List<MainTableColumnModel> columnSortOrder) {
private final boolean dedicatedFileColumnsEnabled;

public ColumnPreferences(List<MainTableColumnModel> columns, List<MainTableColumnModel> columnSortOrder, boolean dedicatedFileColumnsEnabled) {
this.columns = columns;
this.columnSortOrder = columnSortOrder;
this.dedicatedFileColumnsEnabled = dedicatedFileColumnsEnabled;
}

public List<MainTableColumnModel> getColumns() {
Expand All @@ -22,4 +26,9 @@ public List<MainTableColumnModel> getColumns() {
public List<MainTableColumnModel> getColumnSortOrder() {
return columnSortOrder;
}

public boolean isDedicatedFileColumnsEnabled() {
return dedicatedFileColumnsEnabled;
}

}
10 changes: 4 additions & 6 deletions src/main/java/org/jabref/gui/maintable/MainTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -137,16 +137,14 @@ 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))
.findFirst()
.ifPresent(column -> this.getSortOrder().add(column)));

if (mainTablePreferences.getResizeColumnsToFit()) {
this.setColumnResizePolicy(new SmartConstrainedResizePolicy());
}
this.setColumnResizePolicy(new SmartConstrainedResizePolicy());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the if check should still be there, since otherwise the checkbox in the preferences doesn't do anything, right?

Copy link
Member Author

@koppor koppor Dec 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We removed the preference option completely - since this was a hidden feature. That should have been a visible toggle button and not hidden in the preferences.

Moreover #967 said that there should be a single behavior without the need of a configuration flat. We discussed it in length back then - and the implementors think, this is still a good thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We added a guard to really cover both cases

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you don't want to give users the option for the infinite-column behavior that you were supporting above? I don't see any harm in still having the checkbox. But since I don't want to argue again for keeping code while you want to remove it, removing is also fine with me ;-)


this.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

Expand Down Expand Up @@ -329,7 +327,7 @@ private void handleOnDragDetected(TableRow<BibEntryTableViewModel> row, BibEntry

List<BibEntry> 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);
Expand Down
20 changes: 18 additions & 2 deletions src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
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;
import org.jabref.model.util.OptionalUtil;
import org.jabref.preferences.PreferencesService;
Expand Down Expand Up @@ -109,7 +111,21 @@ public MainTableColumnFactory(BibDatabaseContext database,
default:
case NORMALFIELD:
if (!column.getQualifier().isBlank()) {
columns.add(createFieldColumn(column));
TableColumn<BibEntryTableViewModel, ?> fieldColumn = createFieldColumn(column);
columns.add(fieldColumn);
if (column.getQualifier().equalsIgnoreCase(StandardField.YEAR.getName())) {
// 60 is chosen, because of the optimal width of a four digit field
setExactWidth(fieldColumn, 60);
tobiasdiez marked this conversation as resolved.
Show resolved Hide resolved
} 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);
koppor marked this conversation as resolved.
Show resolved Hide resolved
}
}
break;
}
Expand All @@ -125,7 +141,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<BibEntryTableViewModel, String> createIndexColumn(MainTableColumnModel columnModel) {
TableColumn<BibEntryTableViewModel, String> column = new MainTableColumn<>(columnModel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
25 changes: 0 additions & 25 deletions src/main/java/org/jabref/gui/maintable/MainTablePreferences.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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()
));
}
}
180 changes: 132 additions & 48 deletions src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package org.jabref.gui.maintable;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javafx.scene.control.ResizeFeaturesBase;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumnBase;
import javafx.scene.control.TableView;
import javafx.util.Callback;
Expand All @@ -22,75 +23,158 @@ public class SmartConstrainedResizePolicy implements Callback<TableView.ResizeFe

private static final Logger LOGGER = LoggerFactory.getLogger(SmartConstrainedResizePolicy.class);

private Map<TableColumnBase, Double> expansionShare = new HashMap<>();

@Override
public Boolean call(TableView.ResizeFeatures prop) {
if (prop.getColumn() == null) {
return initColumnSize(prop.getTable());
// happens at initialization and at window resize
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you below call setPrefWidth, after a window resize the expansionShare no longer reflect the actual preferred widths.

I think the easiest solution is to use setWidth instead of setPrefWidth below: https://stackoverflow.com/a/53256244/873661

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We argue that the preferred size is the size the user set manually. It is difficult to distinguish whether a preferred size was set by JabRef automatically or by the user. During experiments with the functionality, it seems that maximization of the window and shrinkage and maximization and so on did not change the columns alignment. Thus, we opted to store the currently stored ratios.

For instance, that way when a user resizes the field "title" to twice the size of "author", this ratio is kept when resizing the window.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was worried that you re-calculate the preferred width (e.g. in the initialization code), so this not necessarily need to correspond to the user's wish. But if that's not a problem, then nice.

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 initColumnSize(TableView<?> table) {
double tableWidth = getContentWidth(table);
List<? extends TableColumnBase<?, ?>> 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;
/**
* 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

resize(col, newSize - col.getWidth());
}
Double totalInitialPreferredWidths;
koppor marked this conversation as resolved.
Show resolved Hide resolved
List<? extends TableColumnBase<?, ?>> visibleLeafColumns = table.getVisibleLeafColumns();
totalInitialPreferredWidths = visibleLeafColumns.stream()
.filter(TableColumnBase::isResizable)
koppor marked this conversation as resolved.
Show resolved Hide resolved
.mapToDouble(TableColumnBase::getPrefWidth).sum();
for (TableColumnBase<?, ?> column : visibleLeafColumns) {
if (column.isResizable()) {
expansionShare.put(column, column.getPrefWidth() / totalInitialPreferredWidths);
} else {
expansionShare.put(column, 0d);
koppor marked this conversation as resolved.
Show resolved Hide resolved
}
}

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);
/**
* 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<Double> 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 = column.getMinWidth();
LOGGER.trace("MinWidth {}", minWidth);
newWidth = Math.max(minWidth, oldWidth + delta);
} else {
double maxWidth = column.getMaxWidth();
LOGGER.trace("MaxWidth {}", maxWidth);
newWidth = Math.min(maxWidth, oldWidth + delta);
}
LOGGER.trace("Size: {} -> {}", oldWidth, newWidth);
if (oldWidth == newWidth) {
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<? extends TableColumnBase<?, ?>> visibleLeafColumns = table.getVisibleLeafColumns();
return constrainedResize(prop,
false,
getContentWidth(table) - 2,
visibleLeafColumns);
Double requiredWidth = visibleLeafColumns.stream().mapToDouble(TableColumnBase::getWidth).sum();
koppor marked this conversation as resolved.
Show resolved Hide resolved
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);
koppor marked this conversation as resolved.
Show resolved Hide resolved
Double newSize;
if (col.equals(prop.getColumn())) {
koppor marked this conversation as resolved.
Show resolved Hide resolved
// 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this doesn't result in a consistent table width. Imagine having two columns, with initial preferred widths of 70/30 and a total table width of 100. Now the user extends the first column by 10, so deltaToApply = 10. The first column then indeed has size 80 (as expected) but the second one has newSize = 30 - 0.3 * 10 = 27 although it should be 20.

}
newSize = Math.min(newSize, col.getMaxWidth());
koppor marked this conversation as resolved.
Show resolved Hide resolved
col.setPrefWidth(newSize);
}
});
return true;
}

LOGGER.debug("Window smaller than content");

determineNewWidth(userChosenColumnToResize, delta).ifPresent(newWidth ->
userChosenColumnToResize.setPrefWidth(newWidth));
return true;
}

private Boolean constrainedResize(TableView.ResizeFeatures prop, Boolean isFirstRun, Double contentWidth, List<? extends TableColumnBase<?, ?>> 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;
/**
* 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<? extends TableColumnBase<?, ?>> visibleLeafColumns = table.getVisibleLeafColumns();

Double remainingAvailableWidth = tableWidth - getSumMinWidthOfColumns(table);

for (TableColumnBase<?, ?> col : visibleLeafColumns) {
double share = expansionShare.get(col);
double newSize = col.getMinWidth() + share * remainingAvailableWidth;
koppor marked this conversation as resolved.
Show resolved Hide resolved

// 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);
koppor marked this conversation as resolved.
Show resolved Hide resolved
}
return true;
koppor marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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
Expand Down
Loading