/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.tools;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Arrays;
import java.util.Date;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.look.Colors;
import com.rapidminer.gui.tools.actions.EqualColumnWidthsAction;
import com.rapidminer.gui.tools.actions.FitAllColumnWidthsAction;
import com.rapidminer.gui.tools.actions.FitColumnWidthAction;
import com.rapidminer.gui.tools.actions.RestoreOriginalColumnOrderAction;
import com.rapidminer.gui.tools.actions.SelectColumnAction;
import com.rapidminer.gui.tools.actions.SelectRowAction;
import com.rapidminer.gui.tools.actions.SortByColumnAction;
import com.rapidminer.gui.tools.actions.SortColumnsAccordingToNameAction;
import com.rapidminer.gui.tools.components.ToolTipWindow;
import com.rapidminer.gui.tools.components.ToolTipWindow.TipProvider;
import com.rapidminer.report.Tableable;
import com.rapidminer.tools.ParameterService;
import com.rapidminer.tools.Tools;
import com.rapidminer.tools.container.Pair;
/**
* <p>
* This class extends a JTable in a way that editing is handled like it is expected, i.e. editing is
* properly stopped during focus losts, resizing, or column movement. The current value is then set
* to the model. The only way to abort the value change is by pressing the escape key.
* </p>
*
* <p>
* The extended table is sortable per default. Developers should note that this feature might lead
* to problems if the columns contain different class types and different editors. In this case one
* of the constructors should be used which set the sortable flag to false.
* </p>
*
* @author Ingo Mierswa, Marco Boeck
*/
public class ExtendedJTable extends JTable implements Tableable, MouseListener {
private static final long serialVersionUID = 4840252601155251257L;
private static final int DEFAULT_MAX_ROWS_FOR_SORTING = 100000;
private static final int DEFAULT_COLUMN_WIDTH = 100;
public static final int NO_DATE_FORMAT = -1;
public static final int DATE_FORMAT = 0;
public static final int TIME_FORMAT = 1;
public static final int DATE_TIME_FORMAT = 2;
private final Action ROW_ACTION = new SelectRowAction(this, IconSize.SMALL);
private final Action COLUMN_ACTION = new SelectColumnAction(this, IconSize.SMALL);
private final Action FIT_COLUMN_ACTION = new FitColumnWidthAction(this, IconSize.SMALL);
private final Action FIT_ALL_COLUMNS_ACTION = new FitAllColumnWidthsAction(this, IconSize.SMALL);
private final Action EQUAL_WIDTHS_ACTION = new EqualColumnWidthsAction(this, IconSize.SMALL);
private final Action SORTING_DESCENDING_ACTION = new SortByColumnAction(this, ExtendedJTableSorterModel.DESCENDING,
IconSize.SMALL);
private final Action SORTING_ASCENDING_ACTION = new SortByColumnAction(this, ExtendedJTableSorterModel.ASCENDING,
IconSize.SMALL);
private final Action SORT_COLUMNS_BY_NAME_ACTION = new SortColumnsAccordingToNameAction(this, IconSize.SMALL);
private final Action RESTORE_COLUMN_ORDER_ACTION = new RestoreOriginalColumnOrderAction(this, IconSize.SMALL);
private boolean sortable = true;
private transient CellColorProvider cellColorProvider = new CellColorProviderAlternating();
private boolean useColoredCellRenderer = true;
private transient ColoredTableCellRenderer renderer = new ColoredTableCellRenderer();
private ExtendedJTableSorterModel tableSorter = null;
private ExtendedJScrollPane scrollPaneParent = null;
private ExtendedJTablePacker packer = null;
private boolean fixFirstColumn = false;
private String[] originalOrder = null;
private boolean showPopopUpMenu = true;
private boolean[] cutOnLineBreaks;
private int[] maximalTextLengths;
private boolean rowHighlightingEnabled;
private int rowHighlight = -1;
private int lastColoredHighlightedRow = -1;
private boolean checkHighlight = false;
public ExtendedJTable() {
this(null, true);
}
public ExtendedJTable(final boolean sortable) {
this(null, sortable);
}
public ExtendedJTable(final TableModel model, final boolean sortable) {
this(model, sortable, true);
}
public ExtendedJTable(final TableModel model, final boolean sortable, final boolean columnMovable) {
this(model, sortable, columnMovable, true);
}
public ExtendedJTable(final boolean sortable, final boolean columnMovable, final boolean autoResize) {
this(null, sortable, columnMovable, autoResize);
}
public ExtendedJTable(final TableModel model, final boolean sortable, final boolean columnMovable,
final boolean autoResize) {
this(model, sortable, columnMovable, autoResize, true, false);
}
public ExtendedJTable(final TableModel model, final boolean sortable, final boolean columnMovable,
final boolean autoResize, final boolean useColoredCellRenderer, final boolean fixFirstColumn) {
super();
this.sortable = sortable;
this.useColoredCellRenderer = useColoredCellRenderer;
this.fixFirstColumn = fixFirstColumn;
// allow all kinds of selection (e.g. for copy and paste)
setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
setColumnSelectionAllowed(true);
setRowSelectionAllowed(true);
setRowHeight(Math.max(getRowHeight(), 25));
getTableHeader().setReorderingAllowed(columnMovable);
// necessary in order to fix changes after focus was lost
putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
// auto resize?
if (!autoResize) {
setAutoResizeMode(AUTO_RESIZE_OFF);
} else {
setAutoResizeMode(AUTO_RESIZE_ALL_COLUMNS);
}
if (model != null) {
setModel(model);
}
// add listener for automatically resizing the table for double clicking the header border
getTableHeader().addMouseListener(new ExtendedJTableColumnFitMouseListener());
addMouseListener(this);
// handles the highlighting of the currently hovered row
addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseMoved(MouseEvent e) {
if (!rowHighlightingEnabled) {
return;
}
if (checkHighlight) {
rowHighlight = rowAtPoint(e.getPoint());
if (rowHighlight != lastColoredHighlightedRow) {
repaint();
}
} else {
rowHighlight = -1;
}
}
@Override
public void mouseDragged(MouseEvent e) {
if (!rowHighlightingEnabled) {
return;
}
// row highlight feels weird while dragging
rowHighlight = -1;
}
});
setBorder(BorderFactory.createMatteBorder(0, 1, 1, 1, Colors.TABLE_CELL_BORDER));
}
/** Registers a new {@link ToolTipWindow} on this table. */
public void installToolTip() {
// adding a new extended tool tip window
new ToolTipWindow(new TableToolTipProvider(), this);
setToolTipText(null);
}
protected Object readResolve() {
this.renderer = new ColoredTableCellRenderer();
return this;
}
protected ExtendedJTableSorterModel getTableSorter() {
return this.tableSorter;
}
/**
* Subclasses might overwrite this method which by default simply returns NO_DATE. The returned
* format should be one out of NO_DATE_FORMAT, DATE_FORMAT, TIME_FORMAT, or DATE_TIME_FORMAT.
* This information will be used for the cell renderer.
*/
public int getDateFormat(final int row, final int column) {
return NO_DATE_FORMAT;
}
/**
* The given color provider will be used for the cell renderer. The default method
* implementation returns {@link SwingTools#LIGHTEST_BLUE} and white for alternating rows. If no
* colors should be used at all, set the cell color provider to null or to the default white
* color provider {@link CellColorProviderWhite}.
*/
public void setCellColorProvider(final CellColorProvider cellColorProvider) {
this.cellColorProvider = cellColorProvider;
}
/**
* The returned color provider will be used for the cell renderer. The default method
* implementation returns {@link SwingTools#LIGHTEST_BLUE} and white for alternating rows. If no
* colors should be used at all, set the cell color provider to null or to the default white
* color provider {@link CellColorProviderWhite}.
*/
public CellColorProvider getCellColorProvider() {
return this.cellColorProvider;
}
public void setColoredTableCellRenderer(ColoredTableCellRenderer renderer) {
this.renderer = renderer;
}
public void setSortable(final boolean sortable) {
this.sortable = sortable;
}
public boolean isSortable() {
return sortable;
}
public void setShowPopupMenu(final boolean showPopupMenu) {
this.showPopopUpMenu = showPopupMenu;
}
public void setFixFirstColumnForRearranging(final boolean fixFirstColumn) {
this.fixFirstColumn = fixFirstColumn;
}
public void setMaximalTextLength(final int maximalTextLength) {
Arrays.fill(maximalTextLengths, maximalTextLength);
}
/**
* Sets whether row highlighting (aka darken the row the mouse is currently over) is active or
* not. By default it is active. Can only be activated if {@link #useColoredCellRenderer} is
* {@code true}.
*
* @param enabled
*/
public void setRowHighlighting(boolean enabled) {
if (!useColoredCellRenderer) {
return;
}
rowHighlightingEnabled = enabled;
}
/**
* If row highlighting is enabled (see {@link #setRowHighlighting(boolean)}, returns whether the
* given row is the currently highlighted row.
*
* @param row
* the row to check
* @return {@code true} if it is currently highlighted; {@code false} otherwise
*/
public boolean isRowHighlighted(int row) {
if (!rowHighlightingEnabled) {
return false;
}
return row == rowHighlight;
}
/**
* Sets the last highlighted row.
*
* @param row
* the last highlighted row
*/
public void setLastHighlightedRow(int row) {
if (!rowHighlightingEnabled) {
return;
}
lastColoredHighlightedRow = row;
}
/**
* Returns whether row highlighting (see {@link #setRowHighlighting(boolean)} is enabled.
*
* @return
*/
public boolean isRowHighlighting() {
return rowHighlightingEnabled;
}
public void setMaximalTextLength(final int maximalTextLength, final int column) {
maximalTextLengths[column] = maximalTextLength;
}
public void setCutOnLineBreak(final boolean enable) {
Arrays.fill(cutOnLineBreaks, enable);
}
public void setCutOnLineBreak(final boolean enable, final int column) {
cutOnLineBreaks[column] = enable;
}
@Override
public void setModel(final TableModel model) {
boolean shouldSort = this.sortable && checkIfSortable(model);
if (shouldSort) {
this.tableSorter = new ExtendedJTableSorterModel(model);
this.tableSorter.setTableHeader(getTableHeader());
super.setModel(this.tableSorter);
} else {
super.setModel(model);
this.tableSorter = null;
}
originalOrder = new String[model.getColumnCount()];
for (int c = 0; c < model.getColumnCount(); c++) {
originalOrder[c] = model.getColumnName(c);
}
// initializing arrays for cell renderer settings
cutOnLineBreaks = new boolean[model.getColumnCount()];
maximalTextLengths = new int[model.getColumnCount()];
Arrays.fill(maximalTextLengths, Integer.MAX_VALUE);
model.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(final TableModelEvent e) {
int oldLength = cutOnLineBreaks.length;
if (oldLength != model.getColumnCount()) {
cutOnLineBreaks = Arrays.copyOf(cutOnLineBreaks, model.getColumnCount());
maximalTextLengths = Arrays.copyOf(maximalTextLengths, model.getColumnCount());
if (oldLength < cutOnLineBreaks.length) {
Arrays.fill(cutOnLineBreaks, oldLength, cutOnLineBreaks.length, false);
Arrays.fill(maximalTextLengths, oldLength, cutOnLineBreaks.length, Integer.MAX_VALUE);
}
}
}
});
}
public void setSortingStatus(final int status, final boolean cancelSorting) {
if (getModel() instanceof ExtendedJTableSorterModel) {
ExtendedJTableSorterModel sorterModel = (ExtendedJTableSorterModel) getModel();
JTableHeader h = getTableHeader();
TableColumnModel columnModel = h.getColumnModel();
int viewColumn = getSelectedColumn();
if (viewColumn != -1) {
int column = columnModel.getColumn(viewColumn).getModelIndex();
if (column != -1) {
if (sorterModel.isSorting()) {
if (cancelSorting) {
sorterModel.cancelSorting();
}
}
sorterModel.setSortingStatus(column, status);
}
}
}
}
public void pack() {
packer = new ExtendedJTablePacker(true);
if (isShowing()) {
packer.pack(this);
packer = null;
}
}
@Override
public void addNotify() {
super.addNotify();
if (packer != null) {
packer.pack(this);
packer = null;
}
}
public void unpack() {
JTableHeader header = getTableHeader();
if (header != null) {
for (int c = 0; c < getColumnCount(); c++) {
TableColumn tableColumn = header.getColumnModel().getColumn(c);
header.setResizingColumn(tableColumn); // this line is very important
int width = DEFAULT_COLUMN_WIDTH;
if (getWidth() / width > getColumnCount()) {
width = getWidth() / getColumnCount();
}
tableColumn.setWidth(width);
}
}
}
public void packColumn() {
JTableHeader header = getTableHeader();
if (header != null) {
int col = getSelectedColumn();
if (col >= 0) {
TableColumn tableColumn = header.getColumnModel().getColumn(col);
if (tableColumn != null) {
int width = (int) header.getDefaultRenderer()
.getTableCellRendererComponent(this, tableColumn.getIdentifier(), false, false, -1, col)
.getPreferredSize().getWidth();
int firstRow = 0;
int lastRow = getRowCount();
ExtendedJScrollPane scrollPane = getExtendedScrollPane();
if (scrollPane != null) {
JViewport viewport = scrollPane.getViewport();
Rectangle viewRect = viewport.getViewRect();
if (viewport.getHeight() < getHeight()) {
firstRow = rowAtPoint(new Point(0, viewRect.y));
firstRow = Math.max(0, firstRow);
lastRow = rowAtPoint(new Point(0, viewRect.y + viewRect.height - 1));
lastRow = Math.min(lastRow, getRowCount());
}
}
for (int row = firstRow; row < lastRow; row++) {
int preferedWidth = (int) getCellRenderer(row, col)
.getTableCellRendererComponent(this, getValueAt(row, col), false, false, row, col)
.getPreferredSize().getWidth();
width = Math.max(width, preferedWidth);
}
header.setResizingColumn(tableColumn); // this line is very important
tableColumn.setWidth(width + getIntercellSpacing().width);
}
}
}
}
public void sortColumnsAccordingToNames() {
int offset = 0;
if (fixFirstColumn) {
offset = 1;
}
for (int i = offset; i < getColumnCount(); i++) {
int minIndex = -1;
String minName = null;
for (int j = i; j < getColumnCount(); j++) {
String currentName = getColumnName(j);
if (minName == null || currentName.compareTo(minName) < 0) {
minName = currentName;
minIndex = j;
}
}
moveColumn(minIndex, i);
}
}
public void restoreOriginalColumnOrder() {
for (int i = 0; i < originalOrder.length; i++) {
String nextColumn = originalOrder[i];
for (int j = i; j < getColumnCount(); j++) {
String candidateName = getColumnName(j);
if (nextColumn.equals(candidateName)) {
moveColumn(j, i);
break;
}
}
}
}
@Override
public Dimension getIntercellSpacing() {
Dimension dimension = super.getIntercellSpacing();
dimension.width = dimension.width + 6;
return dimension;
}
private boolean checkIfSortable(final TableModel model) {
int maxSortableRows = DEFAULT_MAX_ROWS_FOR_SORTING;
String maxString = ParameterService.getParameterValue(RapidMinerGUI.PROPERTY_RAPIDMINER_GUI_MAX_SORTABLE_ROWS);
if (maxString != null) {
try {
maxSortableRows = Integer.parseInt(maxString);
} catch (NumberFormatException e) {
// do nothing
}
}
if (model.getRowCount() > maxSortableRows) {
return false;
} else {
return true;
}
}
/** Necessary to properly stopping the editing when a column is moved (dragged). */
@Override
public void columnMoved(final TableColumnModelEvent e) {
if (isEditing()) {
cellEditor.stopCellEditing();
}
super.columnMoved(e);
}
/** Necessary to properly stopping the editing when a column is resized. */
@Override
public void columnMarginChanged(final ChangeEvent e) {
if (isEditing()) {
cellEditor.stopCellEditing();
}
super.columnMarginChanged(e);
}
public boolean shouldUseColoredCellRenderer() {
return this.useColoredCellRenderer;
}
@Override
public TableCellRenderer getCellRenderer(final int row, final int col) {
if (useColoredCellRenderer) {
Color color = null;
CellColorProvider usedColorProvider = getCellColorProvider();
if (usedColorProvider != null) {
color = usedColorProvider.getCellColor(row, col);
}
if (color != null) {
renderer.setColor(color);
}
renderer.setDateFormat(getDateFormat(row, convertColumnIndexToModel(col)));
if (col < maximalTextLengths.length) {
renderer.setMaximalTextLength(maximalTextLengths[col]);
}
if (col < cutOnLineBreaks.length) {
renderer.setCutOnFirstLineBreak(cutOnLineBreaks[col]);
}
return renderer;
} else {
return super.getCellRenderer(row, col);
}
}
/** This method ensures that the correct tool tip for the current table cell is delivered. */
@Override
public String getToolTipText(final MouseEvent e) {
Point p = e.getPoint();
int colIndex = columnAtPoint(p);
int rowIndex = rowAtPoint(p);
return getToolTipText(colIndex, rowIndex);
}
protected String getToolTipText(final int colIndex, final int rowIndex) {
int realColumnIndex = convertColumnIndexToModel(colIndex);
String text = null;
if (rowIndex >= 0 && rowIndex < getRowCount() && realColumnIndex >= 0
&& realColumnIndex < getModel().getColumnCount()) {
Object value = getModel().getValueAt(rowIndex, realColumnIndex);
if (value instanceof Number) {
// display
Number number = (Number) value;
double numberValue = number.doubleValue();
long longValue = Math.round(numberValue);
if (Math.abs(longValue - numberValue) <= Double.MIN_VALUE) {
// for all intents and purposes we consider these integer
text = String.valueOf(longValue);
} else {
// real values here
text = String.valueOf(numberValue);
}
} else {
if (value != null) {
if (value instanceof Date) {
int dateFormat = getDateFormat(rowIndex, realColumnIndex);
switch (dateFormat) {
case ExtendedJTable.DATE_FORMAT:
text = Tools.formatDate((Date) value);
break;
case ExtendedJTable.TIME_FORMAT:
text = Tools.formatTime((Date) value);
break;
case ExtendedJTable.DATE_TIME_FORMAT:
text = Tools.formatDateTime((Date) value);
break;
default:
text = value.toString();
break;
}
} else {
text = value.toString();
}
} else {
text = "?";
}
}
}
if (text != null && !text.equals("")) {
return SwingTools.transformToolTipText(text, true);
} else {
return super.getToolTipText();
}
}
/**
* {@link Tableable} Method
*/
@Override
public String getCell(int row, final int column) {
String text = null;
if (getTableHeader() != null) {
if (row == 0) {
// titel row
return getTableHeader().getColumnModel().getColumn(column).getHeaderValue().toString();
} else {
row--;
}
}
// data area
Object value = getModel().getValueAt(row, column);
if (value instanceof Number) {
Number number = (Number) value;
double numberValue = number.doubleValue();
text = Tools.formatIntegerIfPossible(numberValue);
} else {
if (value != null) {
text = value.toString();
} else {
text = "?";
}
}
return text;
}
/**
* {@link Tableable} Method
*/
@Override
public int getColumnNumber() {
return getColumnCount();
}
/**
* {@link Tableable} Method
*/
@Override
public int getRowNumber() {
if (getTableHeader() != null) {
return getRowCount() + 1;
} else {
return getRowCount();
}
}
/**
* {@link Tableable} Method
*/
@Override
public void prepareReporting() {}
/**
* {@link Tableable} Method
*/
@Override
public void finishReporting() {}
/**
* {@link Tableable} Method
*/
@Override
public boolean isFirstLineHeader() {
return false;
}
/**
* {@link Tableable} Method
*/
@Override
public boolean isFirstColumnHeader() {
return false;
}
@Override
public boolean getScrollableTracksViewportWidth() {
// in case of the auto resize for all columns, either make the table fill the available
// space or grow the needed space
// this fixes the issue that otherwise it will never exceed the viewport width regardless of
// column count
if (autoResizeMode == AUTO_RESIZE_ALL_COLUMNS) {
return getPreferredSize().width < getParent().getWidth();
}
return super.getScrollableTracksViewportWidth();
}
/**
* Converts the index of the row in the view to the corresponding row in the original model.
* They might difer if the table is sorted.
*
* @param rowIndex
* The index of the row in the view.
* @return The index of the row in the original model.
*/
public int getModelIndex(final int rowIndex) {
if (tableSorter != null) {
return tableSorter.modelIndex(rowIndex);
}
return rowIndex;
}
public void setExtendedScrollPane(final ExtendedJScrollPane scrollPane) {
this.scrollPaneParent = scrollPane;
}
public ExtendedJScrollPane getExtendedScrollPane() {
return this.scrollPaneParent;
}
public void selectCompleteRow() {
addColumnSelectionInterval(0, getColumnCount() - 1);
}
public void selectCompleteColumn() {
addRowSelectionInterval(0, getRowCount() - 1);
}
@Override
public void mouseEntered(final MouseEvent e) {
if (!rowHighlightingEnabled) {
return;
}
checkHighlight = true;
repaint();
}
@Override
public void mouseExited(final MouseEvent e) {
if (!rowHighlightingEnabled) {
return;
}
checkHighlight = false;
rowHighlight = -1;
repaint();
}
@Override
public void mouseClicked(final MouseEvent e) {
mouseReleased(e);
}
@Override
public void mousePressed(final MouseEvent e) {
mouseReleased(e);
}
@Override
public void mouseReleased(final MouseEvent e) {
if (showPopopUpMenu) {
if (e.isPopupTrigger()) {
Point p = e.getPoint();
int row = rowAtPoint(p);
int c = columnAtPoint(p);
// don't do anything when outside of table
if (row < 0 || c < 0) {
return;
}
// only set cell selection if clicked cell is outside current selection
if (row < getSelectedRow() || row > getSelectedRow() + getSelectedRowCount() - 1 || c < getSelectedColumn()
|| c > getSelectedColumn() + getSelectedColumnCount() - 1) {
if (row < getRowCount() && c < getColumnCount()) {
// needed because sometimes row could be outside [0, getRowCount()-1]
setRowSelectionInterval(row, row);
setColumnSelectionInterval(c, c);
}
}
JPopupMenu menu = createPopupMenu();
showPopupMenu(menu, e.getPoint());
}
}
}
protected void showPopupMenu(final JPopupMenu menu, final Point location) {
menu.show(this, (int) location.getX(), (int) location.getY());
}
public JPopupMenu createPopupMenu() {
JPopupMenu menu = new JPopupMenu();
populatePopupMenu(menu);
return menu;
}
public void populatePopupMenu(final JPopupMenu menu) {
menu.add(ROW_ACTION);
menu.add(COLUMN_ACTION);
if (getTableHeader() != null) {
menu.addSeparator();
menu.add(FIT_COLUMN_ACTION);
menu.add(FIT_ALL_COLUMNS_ACTION);
menu.add(EQUAL_WIDTHS_ACTION);
}
if (isSortable()) {
menu.addSeparator();
menu.add(SORTING_ASCENDING_ACTION);
menu.add(SORTING_DESCENDING_ACTION);
}
if (getTableHeader() != null) {
if (getTableHeader().getReorderingAllowed()) {
menu.addSeparator();
menu.add(SORT_COLUMNS_BY_NAME_ACTION);
menu.add(RESTORE_COLUMN_ORDER_ACTION);
}
}
}
private class TableToolTipProvider implements TipProvider {
@Override
public Component getCustomComponent(final Object id) {
return null;
}
@Override
public Object getIdUnder(final Point point) {
Pair<Integer, Integer> cellId = new Pair<>(columnAtPoint(point), rowAtPoint(point));
return cellId;
}
@SuppressWarnings("unchecked")
@Override
public String getTip(final Object id) {
Pair<Integer, Integer> cellId = (Pair<Integer, Integer>) id;
return getToolTipText(cellId.getFirst(), cellId.getSecond());
}
}
}