mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 04:05:39 +00:00
Merge remote-tracking branch 'origin/GP-1-dragonmacher-table-column-visual--SQUASHED'
This commit is contained in:
commit
b641a822d3
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -16,22 +16,22 @@
|
||||
package docking.widgets.table;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.text.AttributedString;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.table.*;
|
||||
|
||||
import generic.theme.*;
|
||||
import generic.theme.GIcon;
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
import generic.theme.Gui;
|
||||
import resources.*;
|
||||
import resources.icons.EmptyIcon;
|
||||
import resources.icons.TranslateIcon;
|
||||
|
||||
public class GTableHeaderRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
private static final Color SORT_NUMBER_FG_COLOR = new GColor("color.fg");
|
||||
|
||||
private static final int PADDING_FOR_COLUMN_NUMBER = 8;
|
||||
private static final Icon UP_ICON =
|
||||
ResourceManager.getScaledIcon(Icons.SORT_ASCENDING_ICON, 14, 14);
|
||||
@ -47,10 +47,23 @@ public class GTableHeaderRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
private Icon primaryIcon = EMPTY_ICON;
|
||||
private Icon helpIcon = EMPTY_ICON;
|
||||
protected boolean isPaintingPrimarySortColumn;
|
||||
private double sortEmphasis = -1;
|
||||
private Image sortImage; // cached image
|
||||
|
||||
private Component rendererComponent;
|
||||
|
||||
/**
|
||||
* Sets the an emphasis value for this column that is used to slightly enlarge and call out the
|
||||
* sort for the column.
|
||||
* @param sortEmphasis the emphasis value
|
||||
*/
|
||||
public void setSortEmphasis(double sortEmphasis) {
|
||||
this.sortEmphasis = sortEmphasis;
|
||||
if (sortEmphasis < 0) {
|
||||
sortImage = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
|
||||
boolean hasFocus, int row, int column) {
|
||||
@ -147,6 +160,30 @@ public class GTableHeaderRenderer extends DefaultTableCellRenderer {
|
||||
return clippedText;
|
||||
}
|
||||
|
||||
// creates an image from the given icon; used scaling the image
|
||||
private Image createImage(Icon icon) {
|
||||
|
||||
if (sortImage != null) {
|
||||
return sortImage;
|
||||
}
|
||||
|
||||
int w = icon.getIconWidth();
|
||||
int h = icon.getIconHeight();
|
||||
|
||||
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = (Graphics2D) bi.getGraphics();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
icon.paintIcon(this, g2d, 0, 0);
|
||||
|
||||
g2d.dispose();
|
||||
|
||||
sortImage = bi;
|
||||
return bi;
|
||||
}
|
||||
|
||||
// We have overridden paint children to add the sort column icon and the help icon, depending
|
||||
// on if this column is sorted and/or hovered.
|
||||
@Override
|
||||
protected void paintChildren(Graphics g) {
|
||||
|
||||
@ -155,9 +192,131 @@ public class GTableHeaderRenderer extends DefaultTableCellRenderer {
|
||||
int offset = 4;
|
||||
int x = helpPoint.x - primaryIcon.getIconWidth() - offset;
|
||||
int y = getIconStartY(primaryIcon.getIconHeight());
|
||||
primaryIcon.paintIcon(this, g, x, y);
|
||||
|
||||
helpIcon.paintIcon(this, g, helpPoint.x, helpPoint.y);
|
||||
if (sortEmphasis <= 1.0) {
|
||||
// default icon painting; no emphasis
|
||||
primaryIcon.paintIcon(this, g, x, y);
|
||||
helpIcon.paintIcon(this, g, helpPoint.x, helpPoint.y);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// This column has been emphasized. We use the notion of emphasis to remind users that they
|
||||
// are using a multi-column sort. When users are toggling the sort direction of a given
|
||||
// column, it is easy to forget that other columns are also sorted, especially when those
|
||||
// columns are in the users peripheral vision. TableUtils uses an animator to control the
|
||||
// emphasis for all sorted columns, other than the clicked column. We hope that this
|
||||
// emphasis creates enough movement in the users peripheral vision to serve as a gentle
|
||||
// reminder that the table sort consists of more than just the clicked column.
|
||||
//
|
||||
// There is no emphasis applied to columns when only a single column is sorted. See the
|
||||
// paint method for details on how the emphasis is used.
|
||||
//
|
||||
|
||||
// create an image and use the graphics for painting the scaled/emphasized version
|
||||
Image image = createImage(primaryIcon);
|
||||
paintImage((Graphics2D) g, image, x, y);
|
||||
}
|
||||
|
||||
// x,y are relative to the end of the component using 0,0
|
||||
private void paintImage(Graphics2D g2d, Image image, int x, int y) {
|
||||
|
||||
//
|
||||
// Currently, the sort emphasis is used to scale the sort icon. This code will scale the
|
||||
// icon, up to a maximum. The emphasis set on this column will grow and then shrink as the
|
||||
// values are updated by an animator. The icon image being painted here will start at the
|
||||
// current icon location, grow to the max emphasis, and then shrink back to its original
|
||||
// size.
|
||||
//
|
||||
double max = 1.3D;
|
||||
double scale = sortEmphasis;
|
||||
scale = Math.min(max, scale);
|
||||
|
||||
AffineTransform originalTransform = g2d.getTransform();
|
||||
try {
|
||||
|
||||
AffineTransform cloned = (AffineTransform) originalTransform.clone();
|
||||
cloned.scale(scale, scale);
|
||||
|
||||
g2d.setTransform(cloned);
|
||||
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// center the growing icon over the normal icon using the size delta and dividing by 2
|
||||
int iw = image.getWidth(null);
|
||||
int ih = image.getHeight(null);
|
||||
double dw = (iw * scale) - iw;
|
||||
double dh = (ih * scale) - ih;
|
||||
double halfDw = dw / 2;
|
||||
double halfDh = dh / 2;
|
||||
|
||||
// as the image grows, we must move x,y back so it stays centered
|
||||
double sx = x / scale;
|
||||
double sy = y / scale;
|
||||
int fx = (int) Math.round(sx - halfDw);
|
||||
int fy = (int) Math.round(sy - halfDh);
|
||||
|
||||
// to make the icon change more noticeable to the user, paint a small highlight behind
|
||||
// the icon being emphasized
|
||||
paintBgHighlight(g2d, scale, max, fx, fy, iw, ih);
|
||||
|
||||
g2d.drawImage(image, fx, fy, null);
|
||||
}
|
||||
finally {
|
||||
g2d.setTransform(originalTransform);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints a background highlight under the icon that will get painted.
|
||||
* @param g2d the graphics
|
||||
* @param scale the current scale, up to max
|
||||
* @param max the max emphasis size
|
||||
* @param ix the icon x
|
||||
* @param iy the icon y
|
||||
* @param iw the icon width
|
||||
* @param ih the icon height
|
||||
*/
|
||||
private void paintBgHighlight(Graphics2D g2d, double scale, double max, double ix, double iy,
|
||||
double iw, double ih) {
|
||||
|
||||
// range from 0 to max (e.g., 0 to .3); highlight will fade in from full alpha
|
||||
double range = max - 1;
|
||||
double current = scale - 1;
|
||||
double alpha = current;
|
||||
|
||||
Composite originalComposite = g2d.getComposite();
|
||||
try {
|
||||
AlphaComposite alphaComposite = AlphaComposite.getInstance(
|
||||
AlphaComposite.SrcOver.getRule(), (float) alpha);
|
||||
|
||||
g2d.setComposite(alphaComposite);
|
||||
g2d.setColor(Colors.FOREGROUND);
|
||||
|
||||
// highlight size is a range from 0 to max, where max is currently .3; grow the
|
||||
// highlight shape as the animation progresses
|
||||
double percent = current / range;
|
||||
double bgpadding = 1;
|
||||
double fullbgw = iw;
|
||||
double fullbgh = ih;
|
||||
double bgw = (fullbgw + bgpadding) * percent;
|
||||
double bgh = (fullbgh + bgpadding) * percent;
|
||||
|
||||
// center using the delta between the icon size and the current highlight size
|
||||
double halfpadding = (bgpadding / 2);
|
||||
double bgwd = (iw + bgpadding) - bgw;
|
||||
double bghd = (ih + bgpadding) - bgh;
|
||||
double halfw = bgwd / 2;
|
||||
double halfh = bghd / 2;
|
||||
double bgx = (ix - halfpadding) + halfw;
|
||||
double bgy = (iy - halfpadding) + halfh;
|
||||
|
||||
g2d.fillRoundRect((int) bgx, (int) bgy, (int) bgw, (int) bgh, 6, 6);
|
||||
}
|
||||
finally {
|
||||
g2d.setComposite(originalComposite);
|
||||
}
|
||||
}
|
||||
|
||||
private Point getHelpIconLocation() {
|
||||
@ -293,25 +452,19 @@ public class GTableHeaderRenderer extends DefaultTableCellRenderer {
|
||||
private Icon getColumnIconForSortState(TableSortState columnSortStates,
|
||||
ColumnSortState sortState, boolean isPendingSort) {
|
||||
|
||||
if (isPendingSort) {
|
||||
return PENDING_ICON;
|
||||
}
|
||||
|
||||
Icon icon = (sortState.isAscending() ? UP_ICON : DOWN_ICON);
|
||||
if (columnSortStates.getSortedColumnCount() != 1) {
|
||||
MultiIcon multiIcon = new MultiIcon(icon);
|
||||
int sortOrder = sortState.getSortOrder();
|
||||
if (sortOrder == 1) {
|
||||
isPaintingPrimarySortColumn = true;
|
||||
}
|
||||
String numberString = Integer.toString(sortOrder);
|
||||
multiIcon.addIcon(new NumberPainterIcon(icon.getIconWidth() + PADDING_FOR_COLUMN_NUMBER,
|
||||
icon.getIconHeight(), numberString));
|
||||
icon = multiIcon;
|
||||
}
|
||||
else {
|
||||
isPaintingPrimarySortColumn = true;
|
||||
}
|
||||
|
||||
if (isPendingSort) {
|
||||
icon = PENDING_ICON;
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
@ -322,6 +475,7 @@ public class GTableHeaderRenderer extends DefaultTableCellRenderer {
|
||||
int middle = height / 2;
|
||||
int halfHeight = iconHeight / 2;
|
||||
int y = middle - halfHeight;
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
@ -362,26 +516,20 @@ public class GTableHeaderRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
Font font = Gui.getFont(FONT_ID);
|
||||
g.setFont(font);
|
||||
g.setColor(Colors.FOREGROUND);
|
||||
FontMetrics fontMetrics = g.getFontMetrics();
|
||||
int numberHeight = fontMetrics.getAscent();
|
||||
|
||||
int padding = 2;
|
||||
|
||||
// draw the number on the right...
|
||||
int padding = 2;
|
||||
int startX = x + (iconWidth - numberWidth) + padding;
|
||||
|
||||
// ...and at the same start y as the sort icon
|
||||
int iconY = getIconStartY(iconHeight);
|
||||
int textBaseline = iconY + numberHeight - padding;
|
||||
|
||||
AttributedString as = new AttributedString(numberText);
|
||||
as.addAttribute(TextAttribute.FOREGROUND, SORT_NUMBER_FG_COLOR);
|
||||
as.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
|
||||
as.addAttribute(TextAttribute.FAMILY, font.getFamily());
|
||||
as.addAttribute(TextAttribute.SIZE, font.getSize2D());
|
||||
|
||||
g.drawString(as.getIterator(), startX, textBaseline);
|
||||
// note: padding here helps make up the difference between the number's actual height
|
||||
// and the font metrics ascent
|
||||
int heightPadding = 2;
|
||||
int absoluteY = y + numberHeight - heightPadding;
|
||||
|
||||
g.drawString(numberText, startX, absoluteY);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -44,26 +44,27 @@ public class TableSortState implements Iterable<ColumnSortState> {
|
||||
/**
|
||||
* Creates a sort state with the given column as the sorted column (sorted ascending).
|
||||
*
|
||||
* @param columnIndex The column to sort
|
||||
* @param columnModelIndex The column to sort
|
||||
* @return a sort state with the given column as the sorted column (sorted ascending).
|
||||
* @see TableSortStateEditor
|
||||
*/
|
||||
public static TableSortState createDefaultSortState(int columnIndex) {
|
||||
return new TableSortState(new ColumnSortState(columnIndex, SortDirection.ASCENDING, 1));
|
||||
public static TableSortState createDefaultSortState(int columnModelIndex) {
|
||||
return new TableSortState(
|
||||
new ColumnSortState(columnModelIndex, SortDirection.ASCENDING, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a sort state with the given column as the sorted column in the given direction.
|
||||
*
|
||||
* @param columnIndex The column to sort
|
||||
* @param columnModelIndex The column to sort
|
||||
* @param isAscending True to sort ascending; false to sort descending
|
||||
* @return a sort state with the given column as the sorted column (sorted ascending).
|
||||
* @see TableSortStateEditor
|
||||
*/
|
||||
public static TableSortState createDefaultSortState(int columnIndex, boolean isAscending) {
|
||||
public static TableSortState createDefaultSortState(int columnModelIndex, boolean isAscending) {
|
||||
SortDirection sortDirection =
|
||||
isAscending ? SortDirection.ASCENDING : SortDirection.DESCENDING;
|
||||
return new TableSortState(new ColumnSortState(columnIndex, sortDirection, 1));
|
||||
return new TableSortState(new ColumnSortState(columnModelIndex, sortDirection, 1));
|
||||
}
|
||||
|
||||
public TableSortState() {
|
||||
@ -111,9 +112,9 @@ public class TableSortState implements Iterable<ColumnSortState> {
|
||||
return columnSortStates.isEmpty();
|
||||
}
|
||||
|
||||
public ColumnSortState getColumnSortState(int columnIndex) {
|
||||
public ColumnSortState getColumnSortState(int columnModelIndex) {
|
||||
for (ColumnSortState sortState : columnSortStates) {
|
||||
if (sortState.getColumnModelIndex() == columnIndex) {
|
||||
if (sortState.getColumnModelIndex() == columnModelIndex) {
|
||||
return sortState;
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -15,15 +15,21 @@
|
||||
*/
|
||||
package docking.widgets.table;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.awt.Graphics;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.*;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.jdesktop.animation.timing.Animator;
|
||||
|
||||
import docking.util.AnimationPainter;
|
||||
import docking.util.AnimationRunner;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.util.bean.GGlassPane;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer.ColumnConstraintFilterMode;
|
||||
|
||||
@ -32,6 +38,11 @@ import ghidra.util.table.column.GColumnRenderer.ColumnConstraintFilterMode;
|
||||
*/
|
||||
public class TableUtils {
|
||||
|
||||
/**
|
||||
* An animation runner that emphasizes the sorted columns using the table's header.
|
||||
*/
|
||||
private static AnimationRunner sortEmphasizingAnimationRunner;
|
||||
|
||||
/**
|
||||
* Select the given row objects. No selection will be made if the objects are filtered out of
|
||||
* view. Passing a {@code null} list or an empty list will clear the selection.
|
||||
@ -56,8 +67,8 @@ public class TableUtils {
|
||||
if (mode == ListSelectionModel.SINGLE_SELECTION) {
|
||||
// take the last item to mimic what the selection model does internally
|
||||
ROW_OBJECT item = items.get(items.size() - 1);
|
||||
@SuppressWarnings({ "cast", "unchecked" })
|
||||
int viewRow = gModel.getRowIndex((ROW_OBJECT) item);
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
int viewRow = gModel.getRowIndex(item);
|
||||
table.setRowSelectionInterval(viewRow, viewRow);
|
||||
return;
|
||||
}
|
||||
@ -68,8 +79,8 @@ public class TableUtils {
|
||||
//
|
||||
List<Integer> rows = new ArrayList<>();
|
||||
for (ROW_OBJECT item : items) {
|
||||
@SuppressWarnings({ "cast", "unchecked" })
|
||||
int viewRow = gModel.getRowIndex((ROW_OBJECT) item);
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
int viewRow = gModel.getRowIndex(item);
|
||||
if (viewRow >= 0) {
|
||||
rows.add(viewRow);
|
||||
}
|
||||
@ -219,8 +230,21 @@ public class TableUtils {
|
||||
editor.addSortedColumn(modelColumnIndex);
|
||||
}
|
||||
|
||||
sortedModel.setTableSortState(editor.createTableSortState());
|
||||
repaintTableHeader(table);
|
||||
TableSortState newSortState = editor.createTableSortState();
|
||||
sortedModel.setTableSortState(newSortState);
|
||||
|
||||
if (sortEmphasizingAnimationRunner != null) {
|
||||
sortEmphasizingAnimationRunner.stop();
|
||||
}
|
||||
|
||||
int n = newSortState.getSortedColumnCount();
|
||||
if (n >= 2) { // don't emphasize a single column
|
||||
sortEmphasizingAnimationRunner =
|
||||
new SortEmphasisAnimationRunner(table, newSortState, columnIndex);
|
||||
sortEmphasizingAnimationRunner.start();
|
||||
}
|
||||
|
||||
repaintTableHeaderForSortChange(table);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -280,8 +304,21 @@ public class TableUtils {
|
||||
editor.addSortedColumn(modelColumnIndex);
|
||||
}
|
||||
|
||||
sortedModel.setTableSortState(editor.createTableSortState());
|
||||
repaintTableHeader(table);
|
||||
TableSortState newSortState = editor.createTableSortState();
|
||||
sortedModel.setTableSortState(newSortState);
|
||||
|
||||
if (sortEmphasizingAnimationRunner != null) {
|
||||
sortEmphasizingAnimationRunner.stop();
|
||||
}
|
||||
|
||||
int n = newSortState.getSortedColumnCount();
|
||||
if (n >= 2) { // don't emphasize a single column
|
||||
sortEmphasizingAnimationRunner =
|
||||
new SortEmphasisAnimationRunner(table, newSortState, columnIndex);
|
||||
sortEmphasizingAnimationRunner.start();
|
||||
}
|
||||
|
||||
repaintTableHeaderForSortChange(table);
|
||||
}
|
||||
|
||||
private static SortedTableModel getSortedTableModel(JTable table) {
|
||||
@ -297,11 +334,181 @@ public class TableUtils {
|
||||
return columnModel.getColumn(columnIndex).getModelIndex();
|
||||
}
|
||||
|
||||
private static void repaintTableHeader(JTable table) {
|
||||
private static void repaintTableHeaderForSortChange(JTable table) {
|
||||
// force an update on the headers so they display the new sorting order
|
||||
JTableHeader tableHeader = table.getTableHeader();
|
||||
if (tableHeader != null) {
|
||||
tableHeader.paintImmediately(tableHeader.getBounds());
|
||||
}
|
||||
}
|
||||
|
||||
private static void resetEmphasis(JTable table) {
|
||||
// clear all emphasis state
|
||||
TableColumnModel columnModel = table.getColumnModel();
|
||||
int n = columnModel.getColumnCount();
|
||||
for (int i = 0; i < n; i++) {
|
||||
TableColumn column = columnModel.getColumn(i);
|
||||
TableCellRenderer renderer = column.getHeaderRenderer();
|
||||
if (renderer instanceof GTableHeaderRenderer gRenderer) {
|
||||
gRenderer.setSortEmphasis(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An animation runner that creates the painter and the values that will be interpollated by
|
||||
* the animator. Each column that is sorted will be emphasized, except for the column that was
|
||||
* clicked, as not to be annoying to the user. The intent of emphasizing the columns is to
|
||||
* signal to the user that other columns are part of the sort, not just the column that was
|
||||
* clicked. We hope that this will remind the user of the overall sort so they are not
|
||||
* confused when the the column that was clicked produces unexpected sort results.
|
||||
*/
|
||||
private static class SortEmphasisAnimationRunner extends AnimationRunner {
|
||||
|
||||
private JTable table;
|
||||
|
||||
public SortEmphasisAnimationRunner(JTable table, TableSortState tableSortState,
|
||||
int clickedColumn) {
|
||||
super(table);
|
||||
this.table = table;
|
||||
|
||||
// Create an array of sort ordinals to use as the values. We need 1 extra value to
|
||||
// create a range between ordinals (e.g., 1-2, 2-3 for 2 ordinals)
|
||||
int n = tableSortState.getSortedColumnCount();
|
||||
int[] ordinals = new int[n];
|
||||
for (int i = 1; i < n + 1; i++) {
|
||||
ordinals[i - 1] = i;
|
||||
}
|
||||
|
||||
// create double values to get a range for the client as the timer calls back
|
||||
Double[] values = new Double[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
values[i] = Double.valueOf(ordinals[i]);
|
||||
}
|
||||
|
||||
EmphasizingSortPainter painter =
|
||||
new EmphasizingSortPainter(table, tableSortState, clickedColumn, ordinals);
|
||||
setPainter(painter);
|
||||
setValues(values);
|
||||
setDuration(Duration.ofSeconds(1));
|
||||
setDoneCallback(this::done);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
Animator animator = createAnimator();
|
||||
|
||||
// acceleration / deceleration make some of the column numbers jiggle, so turn it off
|
||||
animator.setAcceleration(0);
|
||||
animator.setDeceleration(0);
|
||||
super.start();
|
||||
}
|
||||
|
||||
private void done() {
|
||||
resetEmphasis(table);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A painter that will emphasize each sorted column, except for the clicked column, over the
|
||||
* course of an animation. The painter is called with the current emphasis that is passed to
|
||||
* the column along with a repaint request.
|
||||
*/
|
||||
private static class EmphasizingSortPainter implements AnimationPainter {
|
||||
|
||||
private TableSortState tableSortState;
|
||||
private JTable table;
|
||||
private Map<Integer, Integer> columnsByOrdinal = new HashMap<>();
|
||||
private int clickedColumnIndex;
|
||||
|
||||
public EmphasizingSortPainter(JTable table, TableSortState tableSortState,
|
||||
int clickedColumnIndex, int[] ordinals) {
|
||||
this.table = table;
|
||||
this.tableSortState = tableSortState;
|
||||
this.clickedColumnIndex = clickedColumnIndex;
|
||||
|
||||
mapOrdinalsToColumns(ordinals);
|
||||
}
|
||||
|
||||
private void mapOrdinalsToColumns(int[] ordinals) {
|
||||
for (int i = 0; i < ordinals.length; i++) {
|
||||
List<ColumnSortState> sortStates = tableSortState.getAllSortStates();
|
||||
for (ColumnSortState ss : sortStates) {
|
||||
int columnOrdinal = ss.getSortOrder();
|
||||
if (columnOrdinal == ordinals[i]) {
|
||||
int sortColumnIndex = ss.getColumnModelIndex();
|
||||
columnsByOrdinal.put(ordinals[i], sortColumnIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(GGlassPane glassPane, Graphics graphics, double value) {
|
||||
|
||||
JTableHeader tableHeader = table.getTableHeader();
|
||||
if (tableHeader == null) {
|
||||
return; // not sure if this can happen
|
||||
}
|
||||
|
||||
resetEmphasis(table);
|
||||
|
||||
ColumnAndRange columnAndRange = getColumnAndRange(value);
|
||||
if (columnAndRange == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TableColumnModel columnModel = table.getColumnModel();
|
||||
int columnViewIndex = table.convertColumnIndexToView(columnAndRange.column());
|
||||
TableColumn column = columnModel.getColumn(columnViewIndex);
|
||||
TableCellRenderer renderer = column.getHeaderRenderer();
|
||||
if (!(renderer instanceof GTableHeaderRenderer gRenderer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Have the emphasis transition from normal -> large -> normal over the range 0.0 to
|
||||
// 1.1, with an emphasis of 1.x.
|
||||
//
|
||||
double range = columnAndRange.range();
|
||||
double emphasis;
|
||||
if (range < .5) {
|
||||
emphasis = 1 + range;
|
||||
}
|
||||
else {
|
||||
emphasis = 2 - range;
|
||||
}
|
||||
|
||||
gRenderer.setSortEmphasis(emphasis);
|
||||
tableHeader.repaint();
|
||||
}
|
||||
|
||||
private ColumnAndRange getColumnAndRange(double value) {
|
||||
//
|
||||
// The values are the sort ordinals: 1, 2, 3, etc, in double form: 1.1, 1.5... Each
|
||||
// value has the ordinal and a range from 0 - .99
|
||||
//
|
||||
BigDecimal bigDecimal = new BigDecimal(String.valueOf(value));
|
||||
int ordinal = bigDecimal.intValue();
|
||||
Integer columnModelIndex = columnsByOrdinal.get(ordinal);
|
||||
if (columnModelIndex >= clickedColumnIndex) {
|
||||
// Ignore the clicked column when emphasizing the header, as to not be distracting
|
||||
// for the column that they are looking at already. Once we have gotten to or past
|
||||
// the clicked column, then choose the next ordinal to emphasize.
|
||||
int nextOrdinal = ordinal + 1;
|
||||
columnModelIndex = columnsByOrdinal.get(nextOrdinal);
|
||||
}
|
||||
|
||||
BigDecimal bigOrdinal = new BigDecimal(ordinal);
|
||||
BigDecimal decimalValue = bigDecimal.subtract(bigOrdinal);
|
||||
return new ColumnAndRange(columnModelIndex, decimalValue.doubleValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple container for a column index and it's range (from 0 to .99)
|
||||
*/
|
||||
private record ColumnAndRange(int column, double range) {}
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user