-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Changes from 6 commits
a1a5ccc
e91bb8d
8f522df
1acd12e
c8caa13
f5e2d9f
bcea66c
6da49b2
dfac705
057ae9f
8834fb6
5b6f0d8
fd80c30
c64b5e3
9f56fd5
d15e90f
7de21ad
8c7cd7a
f7186f3
80557f2
043a0cc
c3b02e3
fd118a6
f0788f6
a1633a1
73d5448
f75831d
c417d7d
496e505
466731f
caf1e23
63af8fd
274e109
910c592
513c68c
286e7f9
caf7232
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
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; | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you below call I think the easiest solution is to use There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} | ||
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 | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 ;-)