You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2218 lines
83 KiB
2218 lines
83 KiB
/*
|
|
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
package javax.swing.plaf.basic;
|
|
|
|
import java.awt.*;
|
|
import java.awt.datatransfer.*;
|
|
import java.awt.dnd.*;
|
|
import java.awt.event.*;
|
|
import java.util.Enumeration;
|
|
import java.util.EventObject;
|
|
import java.util.Hashtable;
|
|
import java.util.TooManyListenersException;
|
|
import javax.swing.*;
|
|
import javax.swing.event.*;
|
|
import javax.swing.plaf.*;
|
|
import javax.swing.text.*;
|
|
import javax.swing.table.*;
|
|
import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
|
|
import sun.swing.SwingUtilities2;
|
|
|
|
|
|
import java.beans.PropertyChangeEvent;
|
|
import java.beans.PropertyChangeListener;
|
|
|
|
import sun.swing.DefaultLookup;
|
|
import sun.swing.UIAction;
|
|
|
|
/**
|
|
* BasicTableUI implementation
|
|
*
|
|
* @author Philip Milne
|
|
* @author Shannon Hickey (drag and drop)
|
|
*/
|
|
public class BasicTableUI extends TableUI
|
|
{
|
|
private static final StringBuilder BASELINE_COMPONENT_KEY =
|
|
new StringBuilder("Table.baselineComponent");
|
|
|
|
//
|
|
// Instance Variables
|
|
//
|
|
|
|
// The JTable that is delegating the painting to this UI.
|
|
protected JTable table;
|
|
protected CellRendererPane rendererPane;
|
|
|
|
// Listeners that are attached to the JTable
|
|
protected KeyListener keyListener;
|
|
protected FocusListener focusListener;
|
|
protected MouseInputListener mouseInputListener;
|
|
|
|
private Handler handler;
|
|
|
|
/**
|
|
* Local cache of Table's client property "Table.isFileList"
|
|
*/
|
|
private boolean isFileList = false;
|
|
|
|
//
|
|
// Helper class for keyboard actions
|
|
//
|
|
|
|
private static class Actions extends UIAction {
|
|
private static final String CANCEL_EDITING = "cancel";
|
|
private static final String SELECT_ALL = "selectAll";
|
|
private static final String CLEAR_SELECTION = "clearSelection";
|
|
private static final String START_EDITING = "startEditing";
|
|
|
|
private static final String NEXT_ROW = "selectNextRow";
|
|
private static final String NEXT_ROW_CELL = "selectNextRowCell";
|
|
private static final String NEXT_ROW_EXTEND_SELECTION =
|
|
"selectNextRowExtendSelection";
|
|
private static final String NEXT_ROW_CHANGE_LEAD =
|
|
"selectNextRowChangeLead";
|
|
private static final String PREVIOUS_ROW = "selectPreviousRow";
|
|
private static final String PREVIOUS_ROW_CELL = "selectPreviousRowCell";
|
|
private static final String PREVIOUS_ROW_EXTEND_SELECTION =
|
|
"selectPreviousRowExtendSelection";
|
|
private static final String PREVIOUS_ROW_CHANGE_LEAD =
|
|
"selectPreviousRowChangeLead";
|
|
|
|
private static final String NEXT_COLUMN = "selectNextColumn";
|
|
private static final String NEXT_COLUMN_CELL = "selectNextColumnCell";
|
|
private static final String NEXT_COLUMN_EXTEND_SELECTION =
|
|
"selectNextColumnExtendSelection";
|
|
private static final String NEXT_COLUMN_CHANGE_LEAD =
|
|
"selectNextColumnChangeLead";
|
|
private static final String PREVIOUS_COLUMN = "selectPreviousColumn";
|
|
private static final String PREVIOUS_COLUMN_CELL =
|
|
"selectPreviousColumnCell";
|
|
private static final String PREVIOUS_COLUMN_EXTEND_SELECTION =
|
|
"selectPreviousColumnExtendSelection";
|
|
private static final String PREVIOUS_COLUMN_CHANGE_LEAD =
|
|
"selectPreviousColumnChangeLead";
|
|
|
|
private static final String SCROLL_LEFT_CHANGE_SELECTION =
|
|
"scrollLeftChangeSelection";
|
|
private static final String SCROLL_LEFT_EXTEND_SELECTION =
|
|
"scrollLeftExtendSelection";
|
|
private static final String SCROLL_RIGHT_CHANGE_SELECTION =
|
|
"scrollRightChangeSelection";
|
|
private static final String SCROLL_RIGHT_EXTEND_SELECTION =
|
|
"scrollRightExtendSelection";
|
|
|
|
private static final String SCROLL_UP_CHANGE_SELECTION =
|
|
"scrollUpChangeSelection";
|
|
private static final String SCROLL_UP_EXTEND_SELECTION =
|
|
"scrollUpExtendSelection";
|
|
private static final String SCROLL_DOWN_CHANGE_SELECTION =
|
|
"scrollDownChangeSelection";
|
|
private static final String SCROLL_DOWN_EXTEND_SELECTION =
|
|
"scrollDownExtendSelection";
|
|
|
|
private static final String FIRST_COLUMN =
|
|
"selectFirstColumn";
|
|
private static final String FIRST_COLUMN_EXTEND_SELECTION =
|
|
"selectFirstColumnExtendSelection";
|
|
private static final String LAST_COLUMN =
|
|
"selectLastColumn";
|
|
private static final String LAST_COLUMN_EXTEND_SELECTION =
|
|
"selectLastColumnExtendSelection";
|
|
|
|
private static final String FIRST_ROW =
|
|
"selectFirstRow";
|
|
private static final String FIRST_ROW_EXTEND_SELECTION =
|
|
"selectFirstRowExtendSelection";
|
|
private static final String LAST_ROW =
|
|
"selectLastRow";
|
|
private static final String LAST_ROW_EXTEND_SELECTION =
|
|
"selectLastRowExtendSelection";
|
|
|
|
// add the lead item to the selection without changing lead or anchor
|
|
private static final String ADD_TO_SELECTION = "addToSelection";
|
|
|
|
// toggle the selected state of the lead item and move the anchor to it
|
|
private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
|
|
|
|
// extend the selection to the lead item
|
|
private static final String EXTEND_TO = "extendTo";
|
|
|
|
// move the anchor to the lead and ensure only that item is selected
|
|
private static final String MOVE_SELECTION_TO = "moveSelectionTo";
|
|
|
|
// give focus to the JTableHeader, if one exists
|
|
private static final String FOCUS_HEADER = "focusHeader";
|
|
|
|
protected int dx;
|
|
protected int dy;
|
|
protected boolean extend;
|
|
protected boolean inSelection;
|
|
|
|
// horizontally, forwards always means right,
|
|
// regardless of component orientation
|
|
protected boolean forwards;
|
|
protected boolean vertically;
|
|
protected boolean toLimit;
|
|
|
|
protected int leadRow;
|
|
protected int leadColumn;
|
|
|
|
Actions(String name) {
|
|
super(name);
|
|
}
|
|
|
|
Actions(String name, int dx, int dy, boolean extend,
|
|
boolean inSelection) {
|
|
super(name);
|
|
|
|
// Actions spcifying true for "inSelection" are
|
|
// fairly sensitive to bad parameter values. They require
|
|
// that one of dx and dy be 0 and the other be -1 or 1.
|
|
// Bogus parameter values could cause an infinite loop.
|
|
// To prevent any problems we massage the params here
|
|
// and complain if we get something we can't deal with.
|
|
if (inSelection) {
|
|
this.inSelection = true;
|
|
|
|
// look at the sign of dx and dy only
|
|
dx = sign(dx);
|
|
dy = sign(dy);
|
|
|
|
// make sure one is zero, but not both
|
|
assert (dx == 0 || dy == 0) && !(dx == 0 && dy == 0);
|
|
}
|
|
|
|
this.dx = dx;
|
|
this.dy = dy;
|
|
this.extend = extend;
|
|
}
|
|
|
|
Actions(String name, boolean extend, boolean forwards,
|
|
boolean vertically, boolean toLimit) {
|
|
this(name, 0, 0, extend, false);
|
|
this.forwards = forwards;
|
|
this.vertically = vertically;
|
|
this.toLimit = toLimit;
|
|
}
|
|
|
|
private static int clipToRange(int i, int a, int b) {
|
|
return Math.min(Math.max(i, a), b-1);
|
|
}
|
|
|
|
private void moveWithinTableRange(JTable table, int dx, int dy) {
|
|
leadRow = clipToRange(leadRow+dy, 0, table.getRowCount());
|
|
leadColumn = clipToRange(leadColumn+dx, 0, table.getColumnCount());
|
|
}
|
|
|
|
private static int sign(int num) {
|
|
return (num < 0) ? -1 : ((num == 0) ? 0 : 1);
|
|
}
|
|
|
|
/**
|
|
* Called to move within the selected range of the given JTable.
|
|
* This method uses the table's notion of selection, which is
|
|
* important to allow the user to navigate between items visually
|
|
* selected on screen. This notion may or may not be the same as
|
|
* what could be determined by directly querying the selection models.
|
|
* It depends on certain table properties (such as whether or not
|
|
* row or column selection is allowed). When performing modifications,
|
|
* it is recommended that caution be taken in order to preserve
|
|
* the intent of this method, especially when deciding whether to
|
|
* query the selection models or interact with JTable directly.
|
|
*/
|
|
private boolean moveWithinSelectedRange(JTable table, int dx, int dy,
|
|
ListSelectionModel rsm, ListSelectionModel csm) {
|
|
|
|
// Note: The Actions constructor ensures that only one of
|
|
// dx and dy is 0, and the other is either -1 or 1
|
|
|
|
// find out how many items the table is showing as selected
|
|
// and the range of items to navigate through
|
|
int totalCount;
|
|
int minX, maxX, minY, maxY;
|
|
|
|
boolean rs = table.getRowSelectionAllowed();
|
|
boolean cs = table.getColumnSelectionAllowed();
|
|
|
|
// both column and row selection
|
|
if (rs && cs) {
|
|
totalCount = table.getSelectedRowCount() * table.getSelectedColumnCount();
|
|
minX = csm.getMinSelectionIndex();
|
|
maxX = csm.getMaxSelectionIndex();
|
|
minY = rsm.getMinSelectionIndex();
|
|
maxY = rsm.getMaxSelectionIndex();
|
|
// row selection only
|
|
} else if (rs) {
|
|
totalCount = table.getSelectedRowCount();
|
|
minX = 0;
|
|
maxX = table.getColumnCount() - 1;
|
|
minY = rsm.getMinSelectionIndex();
|
|
maxY = rsm.getMaxSelectionIndex();
|
|
// column selection only
|
|
} else if (cs) {
|
|
totalCount = table.getSelectedColumnCount();
|
|
minX = csm.getMinSelectionIndex();
|
|
maxX = csm.getMaxSelectionIndex();
|
|
minY = 0;
|
|
maxY = table.getRowCount() - 1;
|
|
// no selection allowed
|
|
} else {
|
|
totalCount = 0;
|
|
// A bogus assignment to stop javac from complaining
|
|
// about unitialized values. In this case, these
|
|
// won't even be used.
|
|
minX = maxX = minY = maxY = 0;
|
|
}
|
|
|
|
// For some cases, there is no point in trying to stay within the
|
|
// selected area. Instead, move outside the selection, wrapping at
|
|
// the table boundaries. The cases are:
|
|
boolean stayInSelection;
|
|
|
|
// - nothing selected
|
|
if (totalCount == 0 ||
|
|
// - one item selected, and the lead is already selected
|
|
(totalCount == 1 && table.isCellSelected(leadRow, leadColumn))) {
|
|
|
|
stayInSelection = false;
|
|
|
|
maxX = table.getColumnCount() - 1;
|
|
maxY = table.getRowCount() - 1;
|
|
|
|
// the mins are calculated like this in case the max is -1
|
|
minX = Math.min(0, maxX);
|
|
minY = Math.min(0, maxY);
|
|
} else {
|
|
stayInSelection = true;
|
|
}
|
|
|
|
// the algorithm below isn't prepared to deal with -1 lead/anchor
|
|
// so massage appropriately here first
|
|
if (dy == 1 && leadColumn == -1) {
|
|
leadColumn = minX;
|
|
leadRow = -1;
|
|
} else if (dx == 1 && leadRow == -1) {
|
|
leadRow = minY;
|
|
leadColumn = -1;
|
|
} else if (dy == -1 && leadColumn == -1) {
|
|
leadColumn = maxX;
|
|
leadRow = maxY + 1;
|
|
} else if (dx == -1 && leadRow == -1) {
|
|
leadRow = maxY;
|
|
leadColumn = maxX + 1;
|
|
}
|
|
|
|
// In cases where the lead is not within the search range,
|
|
// we need to bring it within one cell for the the search
|
|
// to work properly. Check these here.
|
|
leadRow = Math.min(Math.max(leadRow, minY - 1), maxY + 1);
|
|
leadColumn = Math.min(Math.max(leadColumn, minX - 1), maxX + 1);
|
|
|
|
// find the next position, possibly looping until it is selected
|
|
do {
|
|
calcNextPos(dx, minX, maxX, dy, minY, maxY);
|
|
} while (stayInSelection && !table.isCellSelected(leadRow, leadColumn));
|
|
|
|
return stayInSelection;
|
|
}
|
|
|
|
/**
|
|
* Find the next lead row and column based on the given
|
|
* dx/dy and max/min values.
|
|
*/
|
|
private void calcNextPos(int dx, int minX, int maxX,
|
|
int dy, int minY, int maxY) {
|
|
|
|
if (dx != 0) {
|
|
leadColumn += dx;
|
|
if (leadColumn > maxX) {
|
|
leadColumn = minX;
|
|
leadRow++;
|
|
if (leadRow > maxY) {
|
|
leadRow = minY;
|
|
}
|
|
} else if (leadColumn < minX) {
|
|
leadColumn = maxX;
|
|
leadRow--;
|
|
if (leadRow < minY) {
|
|
leadRow = maxY;
|
|
}
|
|
}
|
|
} else {
|
|
leadRow += dy;
|
|
if (leadRow > maxY) {
|
|
leadRow = minY;
|
|
leadColumn++;
|
|
if (leadColumn > maxX) {
|
|
leadColumn = minX;
|
|
}
|
|
} else if (leadRow < minY) {
|
|
leadRow = maxY;
|
|
leadColumn--;
|
|
if (leadColumn < minX) {
|
|
leadColumn = maxX;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void actionPerformed(ActionEvent e) {
|
|
String key = getName();
|
|
JTable table = (JTable)e.getSource();
|
|
|
|
ListSelectionModel rsm = table.getSelectionModel();
|
|
leadRow = getAdjustedLead(table, true, rsm);
|
|
|
|
ListSelectionModel csm = table.getColumnModel().getSelectionModel();
|
|
leadColumn = getAdjustedLead(table, false, csm);
|
|
|
|
if (key == SCROLL_LEFT_CHANGE_SELECTION || // Paging Actions
|
|
key == SCROLL_LEFT_EXTEND_SELECTION ||
|
|
key == SCROLL_RIGHT_CHANGE_SELECTION ||
|
|
key == SCROLL_RIGHT_EXTEND_SELECTION ||
|
|
key == SCROLL_UP_CHANGE_SELECTION ||
|
|
key == SCROLL_UP_EXTEND_SELECTION ||
|
|
key == SCROLL_DOWN_CHANGE_SELECTION ||
|
|
key == SCROLL_DOWN_EXTEND_SELECTION ||
|
|
key == FIRST_COLUMN ||
|
|
key == FIRST_COLUMN_EXTEND_SELECTION ||
|
|
key == FIRST_ROW ||
|
|
key == FIRST_ROW_EXTEND_SELECTION ||
|
|
key == LAST_COLUMN ||
|
|
key == LAST_COLUMN_EXTEND_SELECTION ||
|
|
key == LAST_ROW ||
|
|
key == LAST_ROW_EXTEND_SELECTION) {
|
|
if (toLimit) {
|
|
if (vertically) {
|
|
int rowCount = table.getRowCount();
|
|
this.dx = 0;
|
|
this.dy = forwards ? rowCount : -rowCount;
|
|
}
|
|
else {
|
|
int colCount = table.getColumnCount();
|
|
this.dx = forwards ? colCount : -colCount;
|
|
this.dy = 0;
|
|
}
|
|
}
|
|
else {
|
|
if (!(SwingUtilities.getUnwrappedParent(table).getParent() instanceof
|
|
JScrollPane)) {
|
|
return;
|
|
}
|
|
|
|
Dimension delta = table.getParent().getSize();
|
|
|
|
if (vertically) {
|
|
Rectangle r = table.getCellRect(leadRow, 0, true);
|
|
if (forwards) {
|
|
// scroll by at least one cell
|
|
r.y += Math.max(delta.height, r.height);
|
|
} else {
|
|
r.y -= delta.height;
|
|
}
|
|
|
|
this.dx = 0;
|
|
int newRow = table.rowAtPoint(r.getLocation());
|
|
if (newRow == -1 && forwards) {
|
|
newRow = table.getRowCount();
|
|
}
|
|
this.dy = newRow - leadRow;
|
|
}
|
|
else {
|
|
Rectangle r = table.getCellRect(0, leadColumn, true);
|
|
|
|
if (forwards) {
|
|
// scroll by at least one cell
|
|
r.x += Math.max(delta.width, r.width);
|
|
} else {
|
|
r.x -= delta.width;
|
|
}
|
|
|
|
int newColumn = table.columnAtPoint(r.getLocation());
|
|
if (newColumn == -1) {
|
|
boolean ltr = table.getComponentOrientation().isLeftToRight();
|
|
|
|
newColumn = forwards ? (ltr ? table.getColumnCount() : 0)
|
|
: (ltr ? 0 : table.getColumnCount());
|
|
|
|
}
|
|
this.dx = newColumn - leadColumn;
|
|
this.dy = 0;
|
|
}
|
|
}
|
|
}
|
|
if (key == NEXT_ROW || // Navigate Actions
|
|
key == NEXT_ROW_CELL ||
|
|
key == NEXT_ROW_EXTEND_SELECTION ||
|
|
key == NEXT_ROW_CHANGE_LEAD ||
|
|
key == NEXT_COLUMN ||
|
|
key == NEXT_COLUMN_CELL ||
|
|
key == NEXT_COLUMN_EXTEND_SELECTION ||
|
|
key == NEXT_COLUMN_CHANGE_LEAD ||
|
|
key == PREVIOUS_ROW ||
|
|
key == PREVIOUS_ROW_CELL ||
|
|
key == PREVIOUS_ROW_EXTEND_SELECTION ||
|
|
key == PREVIOUS_ROW_CHANGE_LEAD ||
|
|
key == PREVIOUS_COLUMN ||
|
|
key == PREVIOUS_COLUMN_CELL ||
|
|
key == PREVIOUS_COLUMN_EXTEND_SELECTION ||
|
|
key == PREVIOUS_COLUMN_CHANGE_LEAD ||
|
|
// Paging Actions.
|
|
key == SCROLL_LEFT_CHANGE_SELECTION ||
|
|
key == SCROLL_LEFT_EXTEND_SELECTION ||
|
|
key == SCROLL_RIGHT_CHANGE_SELECTION ||
|
|
key == SCROLL_RIGHT_EXTEND_SELECTION ||
|
|
key == SCROLL_UP_CHANGE_SELECTION ||
|
|
key == SCROLL_UP_EXTEND_SELECTION ||
|
|
key == SCROLL_DOWN_CHANGE_SELECTION ||
|
|
key == SCROLL_DOWN_EXTEND_SELECTION ||
|
|
key == FIRST_COLUMN ||
|
|
key == FIRST_COLUMN_EXTEND_SELECTION ||
|
|
key == FIRST_ROW ||
|
|
key == FIRST_ROW_EXTEND_SELECTION ||
|
|
key == LAST_COLUMN ||
|
|
key == LAST_COLUMN_EXTEND_SELECTION ||
|
|
key == LAST_ROW ||
|
|
key == LAST_ROW_EXTEND_SELECTION) {
|
|
|
|
if (table.isEditing() &&
|
|
!table.getCellEditor().stopCellEditing()) {
|
|
return;
|
|
}
|
|
|
|
// Unfortunately, this strategy introduces bugs because
|
|
// of the asynchronous nature of requestFocus() call below.
|
|
// Introducing a delay with invokeLater() makes this work
|
|
// in the typical case though race conditions then allow
|
|
// focus to disappear altogether. The right solution appears
|
|
// to be to fix requestFocus() so that it queues a request
|
|
// for the focus regardless of who owns the focus at the
|
|
// time the call to requestFocus() is made. The optimisation
|
|
// to ignore the call to requestFocus() when the component
|
|
// already has focus may ligitimately be made as the
|
|
// request focus event is dequeued, not before.
|
|
|
|
// boolean wasEditingWithFocus = table.isEditing() &&
|
|
// table.getEditorComponent().isFocusOwner();
|
|
|
|
boolean changeLead = false;
|
|
if (key == NEXT_ROW_CHANGE_LEAD || key == PREVIOUS_ROW_CHANGE_LEAD) {
|
|
changeLead = (rsm.getSelectionMode()
|
|
== ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
|
} else if (key == NEXT_COLUMN_CHANGE_LEAD || key == PREVIOUS_COLUMN_CHANGE_LEAD) {
|
|
changeLead = (csm.getSelectionMode()
|
|
== ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
|
}
|
|
|
|
if (changeLead) {
|
|
moveWithinTableRange(table, dx, dy);
|
|
if (dy != 0) {
|
|
// casting should be safe since the action is only enabled
|
|
// for DefaultListSelectionModel
|
|
((DefaultListSelectionModel)rsm).moveLeadSelectionIndex(leadRow);
|
|
if (getAdjustedLead(table, false, csm) == -1
|
|
&& table.getColumnCount() > 0) {
|
|
|
|
((DefaultListSelectionModel)csm).moveLeadSelectionIndex(0);
|
|
}
|
|
} else {
|
|
// casting should be safe since the action is only enabled
|
|
// for DefaultListSelectionModel
|
|
((DefaultListSelectionModel)csm).moveLeadSelectionIndex(leadColumn);
|
|
if (getAdjustedLead(table, true, rsm) == -1
|
|
&& table.getRowCount() > 0) {
|
|
|
|
((DefaultListSelectionModel)rsm).moveLeadSelectionIndex(0);
|
|
}
|
|
}
|
|
|
|
Rectangle cellRect = table.getCellRect(leadRow, leadColumn, false);
|
|
if (cellRect != null) {
|
|
table.scrollRectToVisible(cellRect);
|
|
}
|
|
} else if (!inSelection) {
|
|
moveWithinTableRange(table, dx, dy);
|
|
table.changeSelection(leadRow, leadColumn, false, extend);
|
|
}
|
|
else {
|
|
if (table.getRowCount() <= 0 || table.getColumnCount() <= 0) {
|
|
// bail - don't try to move selection on an empty table
|
|
return;
|
|
}
|
|
|
|
if (moveWithinSelectedRange(table, dx, dy, rsm, csm)) {
|
|
// this is the only way we have to set both the lead
|
|
// and the anchor without changing the selection
|
|
if (rsm.isSelectedIndex(leadRow)) {
|
|
rsm.addSelectionInterval(leadRow, leadRow);
|
|
} else {
|
|
rsm.removeSelectionInterval(leadRow, leadRow);
|
|
}
|
|
|
|
if (csm.isSelectedIndex(leadColumn)) {
|
|
csm.addSelectionInterval(leadColumn, leadColumn);
|
|
} else {
|
|
csm.removeSelectionInterval(leadColumn, leadColumn);
|
|
}
|
|
|
|
Rectangle cellRect = table.getCellRect(leadRow, leadColumn, false);
|
|
if (cellRect != null) {
|
|
table.scrollRectToVisible(cellRect);
|
|
}
|
|
}
|
|
else {
|
|
table.changeSelection(leadRow, leadColumn,
|
|
false, false);
|
|
}
|
|
}
|
|
|
|
/*
|
|
if (wasEditingWithFocus) {
|
|
table.editCellAt(leadRow, leadColumn);
|
|
final Component editorComp = table.getEditorComponent();
|
|
if (editorComp != null) {
|
|
SwingUtilities.invokeLater(new Runnable() {
|
|
public void run() {
|
|
editorComp.requestFocus();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
*/
|
|
} else if (key == CANCEL_EDITING) {
|
|
table.removeEditor();
|
|
} else if (key == SELECT_ALL) {
|
|
table.selectAll();
|
|
} else if (key == CLEAR_SELECTION) {
|
|
table.clearSelection();
|
|
} else if (key == START_EDITING) {
|
|
if (!table.hasFocus()) {
|
|
CellEditor cellEditor = table.getCellEditor();
|
|
if (cellEditor != null && !cellEditor.stopCellEditing()) {
|
|
return;
|
|
}
|
|
table.requestFocus();
|
|
return;
|
|
}
|
|
table.editCellAt(leadRow, leadColumn, e);
|
|
Component editorComp = table.getEditorComponent();
|
|
if (editorComp != null) {
|
|
editorComp.requestFocus();
|
|
}
|
|
} else if (key == ADD_TO_SELECTION) {
|
|
if (!table.isCellSelected(leadRow, leadColumn)) {
|
|
int oldAnchorRow = rsm.getAnchorSelectionIndex();
|
|
int oldAnchorColumn = csm.getAnchorSelectionIndex();
|
|
rsm.setValueIsAdjusting(true);
|
|
csm.setValueIsAdjusting(true);
|
|
table.changeSelection(leadRow, leadColumn, true, false);
|
|
rsm.setAnchorSelectionIndex(oldAnchorRow);
|
|
csm.setAnchorSelectionIndex(oldAnchorColumn);
|
|
rsm.setValueIsAdjusting(false);
|
|
csm.setValueIsAdjusting(false);
|
|
}
|
|
} else if (key == TOGGLE_AND_ANCHOR) {
|
|
table.changeSelection(leadRow, leadColumn, true, false);
|
|
} else if (key == EXTEND_TO) {
|
|
table.changeSelection(leadRow, leadColumn, false, true);
|
|
} else if (key == MOVE_SELECTION_TO) {
|
|
table.changeSelection(leadRow, leadColumn, false, false);
|
|
} else if (key == FOCUS_HEADER) {
|
|
JTableHeader th = table.getTableHeader();
|
|
if (th != null) {
|
|
//Set the header's selected column to match the table.
|
|
int col = table.getSelectedColumn();
|
|
if (col >= 0) {
|
|
TableHeaderUI thUI = th.getUI();
|
|
if (thUI instanceof BasicTableHeaderUI) {
|
|
((BasicTableHeaderUI)thUI).selectColumn(col);
|
|
}
|
|
}
|
|
|
|
//Then give the header the focus.
|
|
th.requestFocusInWindow();
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean isEnabled(Object sender) {
|
|
String key = getName();
|
|
|
|
if (sender instanceof JTable &&
|
|
Boolean.TRUE.equals(((JTable)sender).getClientProperty("Table.isFileList"))) {
|
|
if (key == NEXT_COLUMN ||
|
|
key == NEXT_COLUMN_CELL ||
|
|
key == NEXT_COLUMN_EXTEND_SELECTION ||
|
|
key == NEXT_COLUMN_CHANGE_LEAD ||
|
|
key == PREVIOUS_COLUMN ||
|
|
key == PREVIOUS_COLUMN_CELL ||
|
|
key == PREVIOUS_COLUMN_EXTEND_SELECTION ||
|
|
key == PREVIOUS_COLUMN_CHANGE_LEAD ||
|
|
key == SCROLL_LEFT_CHANGE_SELECTION ||
|
|
key == SCROLL_LEFT_EXTEND_SELECTION ||
|
|
key == SCROLL_RIGHT_CHANGE_SELECTION ||
|
|
key == SCROLL_RIGHT_EXTEND_SELECTION ||
|
|
key == FIRST_COLUMN ||
|
|
key == FIRST_COLUMN_EXTEND_SELECTION ||
|
|
key == LAST_COLUMN ||
|
|
key == LAST_COLUMN_EXTEND_SELECTION ||
|
|
key == NEXT_ROW_CELL ||
|
|
key == PREVIOUS_ROW_CELL) {
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (key == CANCEL_EDITING && sender instanceof JTable) {
|
|
return ((JTable)sender).isEditing();
|
|
} else if (key == NEXT_ROW_CHANGE_LEAD ||
|
|
key == PREVIOUS_ROW_CHANGE_LEAD) {
|
|
// discontinuous selection actions are only enabled for
|
|
// DefaultListSelectionModel
|
|
return sender != null &&
|
|
((JTable)sender).getSelectionModel()
|
|
instanceof DefaultListSelectionModel;
|
|
} else if (key == NEXT_COLUMN_CHANGE_LEAD ||
|
|
key == PREVIOUS_COLUMN_CHANGE_LEAD) {
|
|
// discontinuous selection actions are only enabled for
|
|
// DefaultListSelectionModel
|
|
return sender != null &&
|
|
((JTable)sender).getColumnModel().getSelectionModel()
|
|
instanceof DefaultListSelectionModel;
|
|
} else if (key == ADD_TO_SELECTION && sender instanceof JTable) {
|
|
// This action is typically bound to SPACE.
|
|
// If the table is already in an editing mode, SPACE should
|
|
// simply enter a space character into the table, and not
|
|
// select a cell. Likewise, if the lead cell is already selected
|
|
// then hitting SPACE should just enter a space character
|
|
// into the cell and begin editing. In both of these cases
|
|
// this action will be disabled.
|
|
JTable table = (JTable)sender;
|
|
int leadRow = getAdjustedLead(table, true);
|
|
int leadCol = getAdjustedLead(table, false);
|
|
return !(table.isEditing() || table.isCellSelected(leadRow, leadCol));
|
|
} else if (key == FOCUS_HEADER && sender instanceof JTable) {
|
|
JTable table = (JTable)sender;
|
|
return table.getTableHeader() != null;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// The Table's Key listener
|
|
//
|
|
|
|
/**
|
|
* This class should be treated as a "protected" inner class.
|
|
* Instantiate it only within subclasses of {@code BasicTableUI}.
|
|
* <p>As of Java 2 platform v1.3 this class is no longer used.
|
|
* Instead <code>JTable</code>
|
|
* overrides <code>processKeyBinding</code> to dispatch the event to
|
|
* the current <code>TableCellEditor</code>.
|
|
*/
|
|
public class KeyHandler implements KeyListener {
|
|
// NOTE: This class exists only for backward compatibility. All
|
|
// its functionality has been moved into Handler. If you need to add
|
|
// new functionality add it to the Handler, but make sure this
|
|
// class calls into the Handler.
|
|
public void keyPressed(KeyEvent e) {
|
|
getHandler().keyPressed(e);
|
|
}
|
|
|
|
public void keyReleased(KeyEvent e) {
|
|
getHandler().keyReleased(e);
|
|
}
|
|
|
|
public void keyTyped(KeyEvent e) {
|
|
getHandler().keyTyped(e);
|
|
}
|
|
}
|
|
|
|
//
|
|
// The Table's focus listener
|
|
//
|
|
|
|
/**
|
|
* This class should be treated as a "protected" inner class.
|
|
* Instantiate it only within subclasses of {@code BasicTableUI}.
|
|
*/
|
|
public class FocusHandler implements FocusListener {
|
|
// NOTE: This class exists only for backward compatibility. All
|
|
// its functionality has been moved into Handler. If you need to add
|
|
// new functionality add it to the Handler, but make sure this
|
|
// class calls into the Handler.
|
|
public void focusGained(FocusEvent e) {
|
|
getHandler().focusGained(e);
|
|
}
|
|
|
|
public void focusLost(FocusEvent e) {
|
|
getHandler().focusLost(e);
|
|
}
|
|
}
|
|
|
|
//
|
|
// The Table's mouse and mouse motion listeners
|
|
//
|
|
|
|
/**
|
|
* This class should be treated as a "protected" inner class.
|
|
* Instantiate it only within subclasses of BasicTableUI.
|
|
*/
|
|
public class MouseInputHandler implements MouseInputListener {
|
|
// NOTE: This class exists only for backward compatibility. All
|
|
// its functionality has been moved into Handler. If you need to add
|
|
// new functionality add it to the Handler, but make sure this
|
|
// class calls into the Handler.
|
|
public void mouseClicked(MouseEvent e) {
|
|
getHandler().mouseClicked(e);
|
|
}
|
|
|
|
public void mousePressed(MouseEvent e) {
|
|
getHandler().mousePressed(e);
|
|
}
|
|
|
|
public void mouseReleased(MouseEvent e) {
|
|
getHandler().mouseReleased(e);
|
|
}
|
|
|
|
public void mouseEntered(MouseEvent e) {
|
|
getHandler().mouseEntered(e);
|
|
}
|
|
|
|
public void mouseExited(MouseEvent e) {
|
|
getHandler().mouseExited(e);
|
|
}
|
|
|
|
public void mouseMoved(MouseEvent e) {
|
|
getHandler().mouseMoved(e);
|
|
}
|
|
|
|
public void mouseDragged(MouseEvent e) {
|
|
getHandler().mouseDragged(e);
|
|
}
|
|
}
|
|
|
|
private class Handler implements FocusListener, MouseInputListener,
|
|
PropertyChangeListener, ListSelectionListener, ActionListener,
|
|
BeforeDrag {
|
|
|
|
// FocusListener
|
|
private void repaintLeadCell( ) {
|
|
int lr = getAdjustedLead(table, true);
|
|
int lc = getAdjustedLead(table, false);
|
|
|
|
if (lr < 0 || lc < 0) {
|
|
return;
|
|
}
|
|
|
|
Rectangle dirtyRect = table.getCellRect(lr, lc, false);
|
|
table.repaint(dirtyRect);
|
|
}
|
|
|
|
public void focusGained(FocusEvent e) {
|
|
repaintLeadCell();
|
|
}
|
|
|
|
public void focusLost(FocusEvent e) {
|
|
repaintLeadCell();
|
|
}
|
|
|
|
|
|
// KeyListener
|
|
public void keyPressed(KeyEvent e) { }
|
|
|
|
public void keyReleased(KeyEvent e) { }
|
|
|
|
public void keyTyped(KeyEvent e) {
|
|
KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyChar(),
|
|
e.getModifiers());
|
|
|
|
// We register all actions using ANCESTOR_OF_FOCUSED_COMPONENT
|
|
// which means that we might perform the appropriate action
|
|
// in the table and then forward it to the editor if the editor
|
|
// had focus. Make sure this doesn't happen by checking our
|
|
// InputMaps.
|
|
InputMap map = table.getInputMap(JComponent.WHEN_FOCUSED);
|
|
if (map != null && map.get(keyStroke) != null) {
|
|
return;
|
|
}
|
|
map = table.getInputMap(JComponent.
|
|
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
|
if (map != null && map.get(keyStroke) != null) {
|
|
return;
|
|
}
|
|
|
|
keyStroke = KeyStroke.getKeyStrokeForEvent(e);
|
|
|
|
// The AWT seems to generate an unconsumed \r event when
|
|
// ENTER (\n) is pressed.
|
|
if (e.getKeyChar() == '\r') {
|
|
return;
|
|
}
|
|
|
|
int leadRow = getAdjustedLead(table, true);
|
|
int leadColumn = getAdjustedLead(table, false);
|
|
if (leadRow != -1 && leadColumn != -1 && !table.isEditing()) {
|
|
if (!table.editCellAt(leadRow, leadColumn)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Forwarding events this way seems to put the component
|
|
// in a state where it believes it has focus. In reality
|
|
// the table retains focus - though it is difficult for
|
|
// a user to tell, since the caret is visible and flashing.
|
|
|
|
// Calling table.requestFocus() here, to get the focus back to
|
|
// the table, seems to have no effect.
|
|
|
|
Component editorComp = table.getEditorComponent();
|
|
if (table.isEditing() && editorComp != null) {
|
|
if (editorComp instanceof JComponent) {
|
|
JComponent component = (JComponent)editorComp;
|
|
map = component.getInputMap(JComponent.WHEN_FOCUSED);
|
|
Object binding = (map != null) ? map.get(keyStroke) : null;
|
|
if (binding == null) {
|
|
map = component.getInputMap(JComponent.
|
|
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
|
binding = (map != null) ? map.get(keyStroke) : null;
|
|
}
|
|
if (binding != null) {
|
|
ActionMap am = component.getActionMap();
|
|
Action action = (am != null) ? am.get(binding) : null;
|
|
if (action != null && SwingUtilities.
|
|
notifyAction(action, keyStroke, e, component,
|
|
e.getModifiers())) {
|
|
e.consume();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// MouseInputListener
|
|
|
|
// Component receiving mouse events during editing.
|
|
// May not be editorComponent.
|
|
private Component dispatchComponent;
|
|
|
|
public void mouseClicked(MouseEvent e) {}
|
|
|
|
private void setDispatchComponent(MouseEvent e) {
|
|
Component editorComponent = table.getEditorComponent();
|
|
Point p = e.getPoint();
|
|
Point p2 = SwingUtilities.convertPoint(table, p, editorComponent);
|
|
dispatchComponent =
|
|
SwingUtilities.getDeepestComponentAt(editorComponent,
|
|
p2.x, p2.y);
|
|
SwingUtilities2.setSkipClickCount(dispatchComponent,
|
|
e.getClickCount() - 1);
|
|
}
|
|
|
|
private boolean repostEvent(MouseEvent e) {
|
|
// Check for isEditing() in case another event has
|
|
// caused the editor to be removed. See bug #4306499.
|
|
if (dispatchComponent == null || !table.isEditing()) {
|
|
return false;
|
|
}
|
|
MouseEvent e2 = SwingUtilities.convertMouseEvent(table, e,
|
|
dispatchComponent);
|
|
dispatchComponent.dispatchEvent(e2);
|
|
return true;
|
|
}
|
|
|
|
private void setValueIsAdjusting(boolean flag) {
|
|
table.getSelectionModel().setValueIsAdjusting(flag);
|
|
table.getColumnModel().getSelectionModel().
|
|
setValueIsAdjusting(flag);
|
|
}
|
|
|
|
// The row and column where the press occurred and the
|
|
// press event itself
|
|
private int pressedRow;
|
|
private int pressedCol;
|
|
private MouseEvent pressedEvent;
|
|
|
|
// Whether or not the mouse press (which is being considered as part
|
|
// of a drag sequence) also caused the selection change to be fully
|
|
// processed.
|
|
private boolean dragPressDidSelection;
|
|
|
|
// Set to true when a drag gesture has been fully recognized and DnD
|
|
// begins. Use this to ignore further mouse events which could be
|
|
// delivered if DnD is cancelled (via ESCAPE for example)
|
|
private boolean dragStarted;
|
|
|
|
// Whether or not we should start the editing timer on release
|
|
private boolean shouldStartTimer;
|
|
|
|
// To cache the return value of pointOutsidePrefSize since we use
|
|
// it multiple times.
|
|
private boolean outsidePrefSize;
|
|
|
|
// Used to delay the start of editing.
|
|
private Timer timer = null;
|
|
|
|
private boolean canStartDrag() {
|
|
if (pressedRow == -1 || pressedCol == -1) {
|
|
return false;
|
|
}
|
|
|
|
if (isFileList) {
|
|
return !outsidePrefSize;
|
|
}
|
|
|
|
// if this is a single selection table
|
|
if ((table.getSelectionModel().getSelectionMode() ==
|
|
ListSelectionModel.SINGLE_SELECTION) &&
|
|
(table.getColumnModel().getSelectionModel().getSelectionMode() ==
|
|
ListSelectionModel.SINGLE_SELECTION)) {
|
|
|
|
return true;
|
|
}
|
|
|
|
return table.isCellSelected(pressedRow, pressedCol);
|
|
}
|
|
|
|
public void mousePressed(MouseEvent e) {
|
|
if (SwingUtilities2.shouldIgnore(e, table)) {
|
|
return;
|
|
}
|
|
|
|
if (table.isEditing() && !table.getCellEditor().stopCellEditing()) {
|
|
Component editorComponent = table.getEditorComponent();
|
|
if (editorComponent != null && !editorComponent.hasFocus()) {
|
|
SwingUtilities2.compositeRequestFocus(editorComponent);
|
|
}
|
|
return;
|
|
}
|
|
|
|
Point p = e.getPoint();
|
|
pressedRow = table.rowAtPoint(p);
|
|
pressedCol = table.columnAtPoint(p);
|
|
outsidePrefSize = pointOutsidePrefSize(pressedRow, pressedCol, p);
|
|
|
|
if (isFileList) {
|
|
shouldStartTimer =
|
|
table.isCellSelected(pressedRow, pressedCol) &&
|
|
!e.isShiftDown() &&
|
|
!BasicGraphicsUtils.isMenuShortcutKeyDown(e) &&
|
|
!outsidePrefSize;
|
|
}
|
|
|
|
if (table.getDragEnabled()) {
|
|
mousePressedDND(e);
|
|
} else {
|
|
SwingUtilities2.adjustFocus(table);
|
|
if (!isFileList) {
|
|
setValueIsAdjusting(true);
|
|
}
|
|
adjustSelection(e);
|
|
}
|
|
}
|
|
|
|
private void mousePressedDND(MouseEvent e) {
|
|
pressedEvent = e;
|
|
boolean grabFocus = true;
|
|
dragStarted = false;
|
|
|
|
if (canStartDrag() && DragRecognitionSupport.mousePressed(e)) {
|
|
|
|
dragPressDidSelection = false;
|
|
|
|
if (BasicGraphicsUtils.isMenuShortcutKeyDown(e) && isFileList) {
|
|
// do nothing for control - will be handled on release
|
|
// or when drag starts
|
|
return;
|
|
} else if (!e.isShiftDown() && table.isCellSelected(pressedRow, pressedCol)) {
|
|
// clicking on something that's already selected
|
|
// and need to make it the lead now
|
|
table.getSelectionModel().addSelectionInterval(pressedRow,
|
|
pressedRow);
|
|
table.getColumnModel().getSelectionModel().
|
|
addSelectionInterval(pressedCol, pressedCol);
|
|
|
|
return;
|
|
}
|
|
|
|
dragPressDidSelection = true;
|
|
|
|
// could be a drag initiating event - don't grab focus
|
|
grabFocus = false;
|
|
} else if (!isFileList) {
|
|
// When drag can't happen, mouse drags might change the selection in the table
|
|
// so we want the isAdjusting flag to be set
|
|
setValueIsAdjusting(true);
|
|
}
|
|
|
|
if (grabFocus) {
|
|
SwingUtilities2.adjustFocus(table);
|
|
}
|
|
|
|
adjustSelection(e);
|
|
}
|
|
|
|
private void adjustSelection(MouseEvent e) {
|
|
// Fix for 4835633
|
|
if (outsidePrefSize) {
|
|
// If shift is down in multi-select, we should just return.
|
|
// For single select or non-shift-click, clear the selection
|
|
if (e.getID() == MouseEvent.MOUSE_PRESSED &&
|
|
(!e.isShiftDown() ||
|
|
table.getSelectionModel().getSelectionMode() ==
|
|
ListSelectionModel.SINGLE_SELECTION)) {
|
|
table.clearSelection();
|
|
TableCellEditor tce = table.getCellEditor();
|
|
if (tce != null) {
|
|
tce.stopCellEditing();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
// The autoscroller can generate drag events outside the
|
|
// table's range.
|
|
if ((pressedCol == -1) || (pressedRow == -1)) {
|
|
return;
|
|
}
|
|
|
|
boolean dragEnabled = table.getDragEnabled();
|
|
|
|
if (!dragEnabled && !isFileList && table.editCellAt(pressedRow, pressedCol, e)) {
|
|
setDispatchComponent(e);
|
|
repostEvent(e);
|
|
}
|
|
|
|
CellEditor editor = table.getCellEditor();
|
|
if (dragEnabled || editor == null || editor.shouldSelectCell(e)) {
|
|
table.changeSelection(pressedRow, pressedCol,
|
|
BasicGraphicsUtils.isMenuShortcutKeyDown(e),
|
|
e.isShiftDown());
|
|
}
|
|
}
|
|
|
|
public void valueChanged(ListSelectionEvent e) {
|
|
if (timer != null) {
|
|
timer.stop();
|
|
timer = null;
|
|
}
|
|
}
|
|
|
|
public void actionPerformed(ActionEvent ae) {
|
|
table.editCellAt(pressedRow, pressedCol, null);
|
|
Component editorComponent = table.getEditorComponent();
|
|
if (editorComponent != null && !editorComponent.hasFocus()) {
|
|
SwingUtilities2.compositeRequestFocus(editorComponent);
|
|
}
|
|
return;
|
|
}
|
|
|
|
private void maybeStartTimer() {
|
|
if (!shouldStartTimer) {
|
|
return;
|
|
}
|
|
|
|
if (timer == null) {
|
|
timer = new Timer(1200, this);
|
|
timer.setRepeats(false);
|
|
}
|
|
|
|
timer.start();
|
|
}
|
|
|
|
public void mouseReleased(MouseEvent e) {
|
|
if (SwingUtilities2.shouldIgnore(e, table)) {
|
|
return;
|
|
}
|
|
|
|
if (table.getDragEnabled()) {
|
|
mouseReleasedDND(e);
|
|
} else {
|
|
if (isFileList) {
|
|
maybeStartTimer();
|
|
}
|
|
}
|
|
|
|
pressedEvent = null;
|
|
repostEvent(e);
|
|
dispatchComponent = null;
|
|
setValueIsAdjusting(false);
|
|
}
|
|
|
|
private void mouseReleasedDND(MouseEvent e) {
|
|
MouseEvent me = DragRecognitionSupport.mouseReleased(e);
|
|
if (me != null) {
|
|
SwingUtilities2.adjustFocus(table);
|
|
if (!dragPressDidSelection) {
|
|
adjustSelection(me);
|
|
}
|
|
}
|
|
|
|
if (!dragStarted) {
|
|
if (isFileList) {
|
|
maybeStartTimer();
|
|
return;
|
|
}
|
|
|
|
Point p = e.getPoint();
|
|
|
|
if (pressedEvent != null &&
|
|
table.rowAtPoint(p) == pressedRow &&
|
|
table.columnAtPoint(p) == pressedCol &&
|
|
table.editCellAt(pressedRow, pressedCol, pressedEvent)) {
|
|
|
|
setDispatchComponent(pressedEvent);
|
|
repostEvent(pressedEvent);
|
|
|
|
// This may appear completely odd, but must be done for backward
|
|
// compatibility reasons. Developers have been known to rely on
|
|
// a call to shouldSelectCell after editing has begun.
|
|
CellEditor ce = table.getCellEditor();
|
|
if (ce != null) {
|
|
ce.shouldSelectCell(pressedEvent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void mouseEntered(MouseEvent e) {}
|
|
|
|
public void mouseExited(MouseEvent e) {}
|
|
|
|
public void mouseMoved(MouseEvent e) {}
|
|
|
|
public void dragStarting(MouseEvent me) {
|
|
dragStarted = true;
|
|
|
|
if (BasicGraphicsUtils.isMenuShortcutKeyDown(me) && isFileList) {
|
|
table.getSelectionModel().addSelectionInterval(pressedRow,
|
|
pressedRow);
|
|
table.getColumnModel().getSelectionModel().
|
|
addSelectionInterval(pressedCol, pressedCol);
|
|
}
|
|
|
|
pressedEvent = null;
|
|
}
|
|
|
|
public void mouseDragged(MouseEvent e) {
|
|
if (SwingUtilities2.shouldIgnore(e, table)) {
|
|
return;
|
|
}
|
|
|
|
if (table.getDragEnabled() &&
|
|
(DragRecognitionSupport.mouseDragged(e, this) || dragStarted)) {
|
|
|
|
return;
|
|
}
|
|
|
|
repostEvent(e);
|
|
|
|
// Check isFileList:
|
|
// Until we support drag-selection, dragging should not change
|
|
// the selection (act like single-select).
|
|
if (isFileList || table.isEditing()) {
|
|
return;
|
|
}
|
|
|
|
Point p = e.getPoint();
|
|
int row = table.rowAtPoint(p);
|
|
int column = table.columnAtPoint(p);
|
|
// The autoscroller can generate drag events outside the
|
|
// table's range.
|
|
if ((column == -1) || (row == -1)) {
|
|
return;
|
|
}
|
|
|
|
table.changeSelection(row, column,
|
|
BasicGraphicsUtils.isMenuShortcutKeyDown(e), true);
|
|
}
|
|
|
|
|
|
// PropertyChangeListener
|
|
public void propertyChange(PropertyChangeEvent event) {
|
|
String changeName = event.getPropertyName();
|
|
|
|
if ("componentOrientation" == changeName) {
|
|
InputMap inputMap = getInputMap(
|
|
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
|
|
|
SwingUtilities.replaceUIInputMap(table,
|
|
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
|
|
inputMap);
|
|
|
|
JTableHeader header = table.getTableHeader();
|
|
if (header != null) {
|
|
header.setComponentOrientation(
|
|
(ComponentOrientation)event.getNewValue());
|
|
}
|
|
} else if ("dropLocation" == changeName) {
|
|
JTable.DropLocation oldValue = (JTable.DropLocation)event.getOldValue();
|
|
repaintDropLocation(oldValue);
|
|
repaintDropLocation(table.getDropLocation());
|
|
} else if ("Table.isFileList" == changeName) {
|
|
isFileList = Boolean.TRUE.equals(table.getClientProperty("Table.isFileList"));
|
|
table.revalidate();
|
|
table.repaint();
|
|
if (isFileList) {
|
|
table.getSelectionModel().addListSelectionListener(getHandler());
|
|
} else {
|
|
table.getSelectionModel().removeListSelectionListener(getHandler());
|
|
timer = null;
|
|
}
|
|
} else if ("selectionModel" == changeName) {
|
|
if (isFileList) {
|
|
ListSelectionModel old = (ListSelectionModel)event.getOldValue();
|
|
old.removeListSelectionListener(getHandler());
|
|
table.getSelectionModel().addListSelectionListener(getHandler());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void repaintDropLocation(JTable.DropLocation loc) {
|
|
if (loc == null) {
|
|
return;
|
|
}
|
|
|
|
if (!loc.isInsertRow() && !loc.isInsertColumn()) {
|
|
Rectangle rect = table.getCellRect(loc.getRow(), loc.getColumn(), false);
|
|
if (rect != null) {
|
|
table.repaint(rect);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (loc.isInsertRow()) {
|
|
Rectangle rect = extendRect(getHDropLineRect(loc), true);
|
|
if (rect != null) {
|
|
table.repaint(rect);
|
|
}
|
|
}
|
|
|
|
if (loc.isInsertColumn()) {
|
|
Rectangle rect = extendRect(getVDropLineRect(loc), false);
|
|
if (rect != null) {
|
|
table.repaint(rect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns true if the given point is outside the preferredSize of the
|
|
* item at the given row of the table. (Column must be 0).
|
|
* Returns false if the "Table.isFileList" client property is not set.
|
|
*/
|
|
private boolean pointOutsidePrefSize(int row, int column, Point p) {
|
|
if (!isFileList) {
|
|
return false;
|
|
}
|
|
|
|
return SwingUtilities2.pointOutsidePrefSize(table, row, column, p);
|
|
}
|
|
|
|
//
|
|
// Factory methods for the Listeners
|
|
//
|
|
|
|
private Handler getHandler() {
|
|
if (handler == null) {
|
|
handler = new Handler();
|
|
}
|
|
return handler;
|
|
}
|
|
|
|
/**
|
|
* Creates the key listener for handling keyboard navigation in the JTable.
|
|
*/
|
|
protected KeyListener createKeyListener() {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Creates the focus listener for handling keyboard navigation in the JTable.
|
|
*/
|
|
protected FocusListener createFocusListener() {
|
|
return getHandler();
|
|
}
|
|
|
|
/**
|
|
* Creates the mouse listener for the JTable.
|
|
*/
|
|
protected MouseInputListener createMouseInputListener() {
|
|
return getHandler();
|
|
}
|
|
|
|
//
|
|
// The installation/uninstall procedures and support
|
|
//
|
|
|
|
public static ComponentUI createUI(JComponent c) {
|
|
return new BasicTableUI();
|
|
}
|
|
|
|
// Installation
|
|
|
|
public void installUI(JComponent c) {
|
|
table = (JTable)c;
|
|
|
|
rendererPane = new CellRendererPane();
|
|
table.add(rendererPane);
|
|
installDefaults();
|
|
installDefaults2();
|
|
installListeners();
|
|
installKeyboardActions();
|
|
}
|
|
|
|
/**
|
|
* Initialize JTable properties, e.g. font, foreground, and background.
|
|
* The font, foreground, and background properties are only set if their
|
|
* current value is either null or a UIResource, other properties are set
|
|
* if the current value is null.
|
|
*
|
|
* @see #installUI
|
|
*/
|
|
protected void installDefaults() {
|
|
LookAndFeel.installColorsAndFont(table, "Table.background",
|
|
"Table.foreground", "Table.font");
|
|
// JTable's original row height is 16. To correctly display the
|
|
// contents on Linux we should have set it to 18, Windows 19 and
|
|
// Solaris 20. As these values vary so much it's too hard to
|
|
// be backward compatable and try to update the row height, we're
|
|
// therefor NOT going to adjust the row height based on font. If the
|
|
// developer changes the font, it's there responsability to update
|
|
// the row height.
|
|
|
|
LookAndFeel.installProperty(table, "opaque", Boolean.TRUE);
|
|
|
|
Color sbg = table.getSelectionBackground();
|
|
if (sbg == null || sbg instanceof UIResource) {
|
|
sbg = UIManager.getColor("Table.selectionBackground");
|
|
table.setSelectionBackground(sbg != null ? sbg : UIManager.getColor("textHighlight"));
|
|
}
|
|
|
|
Color sfg = table.getSelectionForeground();
|
|
if (sfg == null || sfg instanceof UIResource) {
|
|
sfg = UIManager.getColor("Table.selectionForeground");
|
|
table.setSelectionForeground(sfg != null ? sfg : UIManager.getColor("textHighlightText"));
|
|
}
|
|
|
|
Color gridColor = table.getGridColor();
|
|
if (gridColor == null || gridColor instanceof UIResource) {
|
|
gridColor = UIManager.getColor("Table.gridColor");
|
|
table.setGridColor(gridColor != null ? gridColor : Color.GRAY);
|
|
}
|
|
|
|
// install the scrollpane border
|
|
Container parent = SwingUtilities.getUnwrappedParent(table); // should be viewport
|
|
if (parent != null) {
|
|
parent = parent.getParent(); // should be the scrollpane
|
|
if (parent != null && parent instanceof JScrollPane) {
|
|
LookAndFeel.installBorder((JScrollPane)parent, "Table.scrollPaneBorder");
|
|
}
|
|
}
|
|
|
|
isFileList = Boolean.TRUE.equals(table.getClientProperty("Table.isFileList"));
|
|
}
|
|
|
|
private void installDefaults2() {
|
|
TransferHandler th = table.getTransferHandler();
|
|
if (th == null || th instanceof UIResource) {
|
|
table.setTransferHandler(defaultTransferHandler);
|
|
// default TransferHandler doesn't support drop
|
|
// so we don't want drop handling
|
|
if (table.getDropTarget() instanceof UIResource) {
|
|
table.setDropTarget(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attaches listeners to the JTable.
|
|
*/
|
|
protected void installListeners() {
|
|
focusListener = createFocusListener();
|
|
keyListener = createKeyListener();
|
|
mouseInputListener = createMouseInputListener();
|
|
|
|
table.addFocusListener(focusListener);
|
|
table.addKeyListener(keyListener);
|
|
table.addMouseListener(mouseInputListener);
|
|
table.addMouseMotionListener(mouseInputListener);
|
|
table.addPropertyChangeListener(getHandler());
|
|
if (isFileList) {
|
|
table.getSelectionModel().addListSelectionListener(getHandler());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register all keyboard actions on the JTable.
|
|
*/
|
|
protected void installKeyboardActions() {
|
|
LazyActionMap.installLazyActionMap(table, BasicTableUI.class,
|
|
"Table.actionMap");
|
|
|
|
InputMap inputMap = getInputMap(JComponent.
|
|
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
|
SwingUtilities.replaceUIInputMap(table,
|
|
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
|
|
inputMap);
|
|
}
|
|
|
|
InputMap getInputMap(int condition) {
|
|
if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
|
|
InputMap keyMap =
|
|
(InputMap)DefaultLookup.get(table, this,
|
|
"Table.ancestorInputMap");
|
|
InputMap rtlKeyMap;
|
|
|
|
if (table.getComponentOrientation().isLeftToRight() ||
|
|
((rtlKeyMap = (InputMap)DefaultLookup.get(table, this,
|
|
"Table.ancestorInputMap.RightToLeft")) == null)) {
|
|
return keyMap;
|
|
} else {
|
|
rtlKeyMap.setParent(keyMap);
|
|
return rtlKeyMap;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static void loadActionMap(LazyActionMap map) {
|
|
// IMPORTANT: There is a very close coupling between the parameters
|
|
// passed to the Actions constructor. Only certain parameter
|
|
// combinations are supported. For example, the following Action would
|
|
// not work as expected:
|
|
// new Actions(Actions.NEXT_ROW_CELL, 1, 4, false, true)
|
|
// Actions which move within the selection only (having a true
|
|
// inSelection parameter) require that one of dx or dy be
|
|
// zero and the other be -1 or 1. The point of this warning is
|
|
// that you should be very careful about making sure a particular
|
|
// combination of parameters is supported before changing or
|
|
// adding anything here.
|
|
|
|
map.put(new Actions(Actions.NEXT_COLUMN, 1, 0,
|
|
false, false));
|
|
map.put(new Actions(Actions.NEXT_COLUMN_CHANGE_LEAD, 1, 0,
|
|
false, false));
|
|
map.put(new Actions(Actions.PREVIOUS_COLUMN, -1, 0,
|
|
false, false));
|
|
map.put(new Actions(Actions.PREVIOUS_COLUMN_CHANGE_LEAD, -1, 0,
|
|
false, false));
|
|
map.put(new Actions(Actions.NEXT_ROW, 0, 1,
|
|
false, false));
|
|
map.put(new Actions(Actions.NEXT_ROW_CHANGE_LEAD, 0, 1,
|
|
false, false));
|
|
map.put(new Actions(Actions.PREVIOUS_ROW, 0, -1,
|
|
false, false));
|
|
map.put(new Actions(Actions.PREVIOUS_ROW_CHANGE_LEAD, 0, -1,
|
|
false, false));
|
|
map.put(new Actions(Actions.NEXT_COLUMN_EXTEND_SELECTION,
|
|
1, 0, true, false));
|
|
map.put(new Actions(Actions.PREVIOUS_COLUMN_EXTEND_SELECTION,
|
|
-1, 0, true, false));
|
|
map.put(new Actions(Actions.NEXT_ROW_EXTEND_SELECTION,
|
|
0, 1, true, false));
|
|
map.put(new Actions(Actions.PREVIOUS_ROW_EXTEND_SELECTION,
|
|
0, -1, true, false));
|
|
map.put(new Actions(Actions.SCROLL_UP_CHANGE_SELECTION,
|
|
false, false, true, false));
|
|
map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_SELECTION,
|
|
false, true, true, false));
|
|
map.put(new Actions(Actions.FIRST_COLUMN,
|
|
false, false, false, true));
|
|
map.put(new Actions(Actions.LAST_COLUMN,
|
|
false, true, false, true));
|
|
|
|
map.put(new Actions(Actions.SCROLL_UP_EXTEND_SELECTION,
|
|
true, false, true, false));
|
|
map.put(new Actions(Actions.SCROLL_DOWN_EXTEND_SELECTION,
|
|
true, true, true, false));
|
|
map.put(new Actions(Actions.FIRST_COLUMN_EXTEND_SELECTION,
|
|
true, false, false, true));
|
|
map.put(new Actions(Actions.LAST_COLUMN_EXTEND_SELECTION,
|
|
true, true, false, true));
|
|
|
|
map.put(new Actions(Actions.FIRST_ROW, false, false, true, true));
|
|
map.put(new Actions(Actions.LAST_ROW, false, true, true, true));
|
|
|
|
map.put(new Actions(Actions.FIRST_ROW_EXTEND_SELECTION,
|
|
true, false, true, true));
|
|
map.put(new Actions(Actions.LAST_ROW_EXTEND_SELECTION,
|
|
true, true, true, true));
|
|
|
|
map.put(new Actions(Actions.NEXT_COLUMN_CELL,
|
|
1, 0, false, true));
|
|
map.put(new Actions(Actions.PREVIOUS_COLUMN_CELL,
|
|
-1, 0, false, true));
|
|
map.put(new Actions(Actions.NEXT_ROW_CELL, 0, 1, false, true));
|
|
map.put(new Actions(Actions.PREVIOUS_ROW_CELL,
|
|
0, -1, false, true));
|
|
|
|
map.put(new Actions(Actions.SELECT_ALL));
|
|
map.put(new Actions(Actions.CLEAR_SELECTION));
|
|
map.put(new Actions(Actions.CANCEL_EDITING));
|
|
map.put(new Actions(Actions.START_EDITING));
|
|
|
|
map.put(TransferHandler.getCutAction().getValue(Action.NAME),
|
|
TransferHandler.getCutAction());
|
|
map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
|
|
TransferHandler.getCopyAction());
|
|
map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
|
|
TransferHandler.getPasteAction());
|
|
|
|
map.put(new Actions(Actions.SCROLL_LEFT_CHANGE_SELECTION,
|
|
false, false, false, false));
|
|
map.put(new Actions(Actions.SCROLL_RIGHT_CHANGE_SELECTION,
|
|
false, true, false, false));
|
|
map.put(new Actions(Actions.SCROLL_LEFT_EXTEND_SELECTION,
|
|
true, false, false, false));
|
|
map.put(new Actions(Actions.SCROLL_RIGHT_EXTEND_SELECTION,
|
|
true, true, false, false));
|
|
|
|
map.put(new Actions(Actions.ADD_TO_SELECTION));
|
|
map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
|
|
map.put(new Actions(Actions.EXTEND_TO));
|
|
map.put(new Actions(Actions.MOVE_SELECTION_TO));
|
|
map.put(new Actions(Actions.FOCUS_HEADER));
|
|
}
|
|
|
|
// Uninstallation
|
|
|
|
public void uninstallUI(JComponent c) {
|
|
uninstallDefaults();
|
|
uninstallListeners();
|
|
uninstallKeyboardActions();
|
|
|
|
table.remove(rendererPane);
|
|
rendererPane = null;
|
|
table = null;
|
|
}
|
|
|
|
protected void uninstallDefaults() {
|
|
if (table.getTransferHandler() instanceof UIResource) {
|
|
table.setTransferHandler(null);
|
|
}
|
|
}
|
|
|
|
protected void uninstallListeners() {
|
|
table.removeFocusListener(focusListener);
|
|
table.removeKeyListener(keyListener);
|
|
table.removeMouseListener(mouseInputListener);
|
|
table.removeMouseMotionListener(mouseInputListener);
|
|
table.removePropertyChangeListener(getHandler());
|
|
if (isFileList) {
|
|
table.getSelectionModel().removeListSelectionListener(getHandler());
|
|
}
|
|
|
|
focusListener = null;
|
|
keyListener = null;
|
|
mouseInputListener = null;
|
|
handler = null;
|
|
}
|
|
|
|
protected void uninstallKeyboardActions() {
|
|
SwingUtilities.replaceUIInputMap(table, JComponent.
|
|
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
|
|
SwingUtilities.replaceUIActionMap(table, null);
|
|
}
|
|
|
|
/**
|
|
* Returns the baseline.
|
|
*
|
|
* @throws NullPointerException {@inheritDoc}
|
|
* @throws IllegalArgumentException {@inheritDoc}
|
|
* @see javax.swing.JComponent#getBaseline(int, int)
|
|
* @since 1.6
|
|
*/
|
|
public int getBaseline(JComponent c, int width, int height) {
|
|
super.getBaseline(c, width, height);
|
|
UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
|
|
Component renderer = (Component)lafDefaults.get(
|
|
BASELINE_COMPONENT_KEY);
|
|
if (renderer == null) {
|
|
DefaultTableCellRenderer tcr = new DefaultTableCellRenderer();
|
|
renderer = tcr.getTableCellRendererComponent(
|
|
table, "a", false, false, -1, -1);
|
|
lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
|
|
}
|
|
renderer.setFont(table.getFont());
|
|
int rowMargin = table.getRowMargin();
|
|
return renderer.getBaseline(Integer.MAX_VALUE, table.getRowHeight() -
|
|
rowMargin) + rowMargin / 2;
|
|
}
|
|
|
|
/**
|
|
* Returns an enum indicating how the baseline of the component
|
|
* changes as the size changes.
|
|
*
|
|
* @throws NullPointerException {@inheritDoc}
|
|
* @see javax.swing.JComponent#getBaseline(int, int)
|
|
* @since 1.6
|
|
*/
|
|
public Component.BaselineResizeBehavior getBaselineResizeBehavior(
|
|
JComponent c) {
|
|
super.getBaselineResizeBehavior(c);
|
|
return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
|
|
}
|
|
|
|
//
|
|
// Size Methods
|
|
//
|
|
|
|
private Dimension createTableSize(long width) {
|
|
int height = 0;
|
|
int rowCount = table.getRowCount();
|
|
if (rowCount > 0 && table.getColumnCount() > 0) {
|
|
Rectangle r = table.getCellRect(rowCount-1, 0, true);
|
|
height = r.y + r.height;
|
|
}
|
|
// Width is always positive. The call to abs() is a workaround for
|
|
// a bug in the 1.1.6 JIT on Windows.
|
|
long tmp = Math.abs(width);
|
|
if (tmp > Integer.MAX_VALUE) {
|
|
tmp = Integer.MAX_VALUE;
|
|
}
|
|
return new Dimension((int)tmp, height);
|
|
}
|
|
|
|
/**
|
|
* Return the minimum size of the table. The minimum height is the
|
|
* row height times the number of rows.
|
|
* The minimum width is the sum of the minimum widths of each column.
|
|
*/
|
|
public Dimension getMinimumSize(JComponent c) {
|
|
long width = 0;
|
|
Enumeration enumeration = table.getColumnModel().getColumns();
|
|
while (enumeration.hasMoreElements()) {
|
|
TableColumn aColumn = (TableColumn)enumeration.nextElement();
|
|
width = width + aColumn.getMinWidth();
|
|
}
|
|
return createTableSize(width);
|
|
}
|
|
|
|
/**
|
|
* Return the preferred size of the table. The preferred height is the
|
|
* row height times the number of rows.
|
|
* The preferred width is the sum of the preferred widths of each column.
|
|
*/
|
|
public Dimension getPreferredSize(JComponent c) {
|
|
long width = 0;
|
|
Enumeration enumeration = table.getColumnModel().getColumns();
|
|
while (enumeration.hasMoreElements()) {
|
|
TableColumn aColumn = (TableColumn)enumeration.nextElement();
|
|
width = width + aColumn.getPreferredWidth();
|
|
}
|
|
return createTableSize(width);
|
|
}
|
|
|
|
/**
|
|
* Return the maximum size of the table. The maximum height is the
|
|
* row heighttimes the number of rows.
|
|
* The maximum width is the sum of the maximum widths of each column.
|
|
*/
|
|
public Dimension getMaximumSize(JComponent c) {
|
|
long width = 0;
|
|
Enumeration enumeration = table.getColumnModel().getColumns();
|
|
while (enumeration.hasMoreElements()) {
|
|
TableColumn aColumn = (TableColumn)enumeration.nextElement();
|
|
width = width + aColumn.getMaxWidth();
|
|
}
|
|
return createTableSize(width);
|
|
}
|
|
|
|
//
|
|
// Paint methods and support
|
|
//
|
|
|
|
/** Paint a representation of the <code>table</code> instance
|
|
* that was set in installUI().
|
|
*/
|
|
public void paint(Graphics g, JComponent c) {
|
|
Rectangle clip = g.getClipBounds();
|
|
|
|
Rectangle bounds = table.getBounds();
|
|
// account for the fact that the graphics has already been translated
|
|
// into the table's bounds
|
|
bounds.x = bounds.y = 0;
|
|
|
|
if (table.getRowCount() <= 0 || table.getColumnCount() <= 0 ||
|
|
// this check prevents us from painting the entire table
|
|
// when the clip doesn't intersect our bounds at all
|
|
!bounds.intersects(clip)) {
|
|
|
|
paintDropLines(g);
|
|
return;
|
|
}
|
|
|
|
boolean ltr = table.getComponentOrientation().isLeftToRight();
|
|
|
|
Point upperLeft = clip.getLocation();
|
|
Point lowerRight = new Point(clip.x + clip.width - 1,
|
|
clip.y + clip.height - 1);
|
|
|
|
int rMin = table.rowAtPoint(upperLeft);
|
|
int rMax = table.rowAtPoint(lowerRight);
|
|
// This should never happen (as long as our bounds intersect the clip,
|
|
// which is why we bail above if that is the case).
|
|
if (rMin == -1) {
|
|
rMin = 0;
|
|
}
|
|
// If the table does not have enough rows to fill the view we'll get -1.
|
|
// (We could also get -1 if our bounds don't intersect the clip,
|
|
// which is why we bail above if that is the case).
|
|
// Replace this with the index of the last row.
|
|
if (rMax == -1) {
|
|
rMax = table.getRowCount()-1;
|
|
}
|
|
|
|
int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight);
|
|
int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft);
|
|
// This should never happen.
|
|
if (cMin == -1) {
|
|
cMin = 0;
|
|
}
|
|
// If the table does not have enough columns to fill the view we'll get -1.
|
|
// Replace this with the index of the last column.
|
|
if (cMax == -1) {
|
|
cMax = table.getColumnCount()-1;
|
|
}
|
|
|
|
// Paint the grid.
|
|
paintGrid(g, rMin, rMax, cMin, cMax);
|
|
|
|
// Paint the cells.
|
|
paintCells(g, rMin, rMax, cMin, cMax);
|
|
|
|
paintDropLines(g);
|
|
}
|
|
|
|
private void paintDropLines(Graphics g) {
|
|
JTable.DropLocation loc = table.getDropLocation();
|
|
if (loc == null) {
|
|
return;
|
|
}
|
|
|
|
Color color = UIManager.getColor("Table.dropLineColor");
|
|
Color shortColor = UIManager.getColor("Table.dropLineShortColor");
|
|
if (color == null && shortColor == null) {
|
|
return;
|
|
}
|
|
|
|
Rectangle rect;
|
|
|
|
rect = getHDropLineRect(loc);
|
|
if (rect != null) {
|
|
int x = rect.x;
|
|
int w = rect.width;
|
|
if (color != null) {
|
|
extendRect(rect, true);
|
|
g.setColor(color);
|
|
g.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
}
|
|
if (!loc.isInsertColumn() && shortColor != null) {
|
|
g.setColor(shortColor);
|
|
g.fillRect(x, rect.y, w, rect.height);
|
|
}
|
|
}
|
|
|
|
rect = getVDropLineRect(loc);
|
|
if (rect != null) {
|
|
int y = rect.y;
|
|
int h = rect.height;
|
|
if (color != null) {
|
|
extendRect(rect, false);
|
|
g.setColor(color);
|
|
g.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
}
|
|
if (!loc.isInsertRow() && shortColor != null) {
|
|
g.setColor(shortColor);
|
|
g.fillRect(rect.x, y, rect.width, h);
|
|
}
|
|
}
|
|
}
|
|
|
|
private Rectangle getHDropLineRect(JTable.DropLocation loc) {
|
|
if (!loc.isInsertRow()) {
|
|
return null;
|
|
}
|
|
|
|
int row = loc.getRow();
|
|
int col = loc.getColumn();
|
|
if (col >= table.getColumnCount()) {
|
|
col--;
|
|
}
|
|
|
|
Rectangle rect = table.getCellRect(row, col, true);
|
|
|
|
if (row >= table.getRowCount()) {
|
|
row--;
|
|
Rectangle prevRect = table.getCellRect(row, col, true);
|
|
rect.y = prevRect.y + prevRect.height;
|
|
}
|
|
|
|
if (rect.y == 0) {
|
|
rect.y = -1;
|
|
} else {
|
|
rect.y -= 2;
|
|
}
|
|
|
|
rect.height = 3;
|
|
|
|
return rect;
|
|
}
|
|
|
|
private Rectangle getVDropLineRect(JTable.DropLocation loc) {
|
|
if (!loc.isInsertColumn()) {
|
|
return null;
|
|
}
|
|
|
|
boolean ltr = table.getComponentOrientation().isLeftToRight();
|
|
int col = loc.getColumn();
|
|
Rectangle rect = table.getCellRect(loc.getRow(), col, true);
|
|
|
|
if (col >= table.getColumnCount()) {
|
|
col--;
|
|
rect = table.getCellRect(loc.getRow(), col, true);
|
|
if (ltr) {
|
|
rect.x = rect.x + rect.width;
|
|
}
|
|
} else if (!ltr) {
|
|
rect.x = rect.x + rect.width;
|
|
}
|
|
|
|
if (rect.x == 0) {
|
|
rect.x = -1;
|
|
} else {
|
|
rect.x -= 2;
|
|
}
|
|
|
|
rect.width = 3;
|
|
|
|
return rect;
|
|
}
|
|
|
|
private Rectangle extendRect(Rectangle rect, boolean horizontal) {
|
|
if (rect == null) {
|
|
return rect;
|
|
}
|
|
|
|
if (horizontal) {
|
|
rect.x = 0;
|
|
rect.width = table.getWidth();
|
|
} else {
|
|
rect.y = 0;
|
|
|
|
if (table.getRowCount() != 0) {
|
|
Rectangle lastRect = table.getCellRect(table.getRowCount() - 1, 0, true);
|
|
rect.height = lastRect.y + lastRect.height;
|
|
} else {
|
|
rect.height = table.getHeight();
|
|
}
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
/*
|
|
* Paints the grid lines within <I>aRect</I>, using the grid
|
|
* color set with <I>setGridColor</I>. Paints vertical lines
|
|
* if <code>getShowVerticalLines()</code> returns true and paints
|
|
* horizontal lines if <code>getShowHorizontalLines()</code>
|
|
* returns true.
|
|
*/
|
|
private void paintGrid(Graphics g, int rMin, int rMax, int cMin, int cMax) {
|
|
g.setColor(table.getGridColor());
|
|
|
|
Rectangle minCell = table.getCellRect(rMin, cMin, true);
|
|
Rectangle maxCell = table.getCellRect(rMax, cMax, true);
|
|
Rectangle damagedArea = minCell.union( maxCell );
|
|
|
|
if (table.getShowHorizontalLines()) {
|
|
int tableWidth = damagedArea.x + damagedArea.width;
|
|
int y = damagedArea.y;
|
|
for (int row = rMin; row <= rMax; row++) {
|
|
y += table.getRowHeight(row);
|
|
g.drawLine(damagedArea.x, y - 1, tableWidth - 1, y - 1);
|
|
}
|
|
}
|
|
if (table.getShowVerticalLines()) {
|
|
TableColumnModel cm = table.getColumnModel();
|
|
int tableHeight = damagedArea.y + damagedArea.height;
|
|
int x;
|
|
if (table.getComponentOrientation().isLeftToRight()) {
|
|
x = damagedArea.x;
|
|
for (int column = cMin; column <= cMax; column++) {
|
|
int w = cm.getColumn(column).getWidth();
|
|
x += w;
|
|
g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
|
|
}
|
|
} else {
|
|
x = damagedArea.x;
|
|
for (int column = cMax; column >= cMin; column--) {
|
|
int w = cm.getColumn(column).getWidth();
|
|
x += w;
|
|
g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private int viewIndexForColumn(TableColumn aColumn) {
|
|
TableColumnModel cm = table.getColumnModel();
|
|
for (int column = 0; column < cm.getColumnCount(); column++) {
|
|
if (cm.getColumn(column) == aColumn) {
|
|
return column;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) {
|
|
JTableHeader header = table.getTableHeader();
|
|
TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
|
|
|
|
TableColumnModel cm = table.getColumnModel();
|
|
int columnMargin = cm.getColumnMargin();
|
|
|
|
Rectangle cellRect;
|
|
TableColumn aColumn;
|
|
int columnWidth;
|
|
if (table.getComponentOrientation().isLeftToRight()) {
|
|
for(int row = rMin; row <= rMax; row++) {
|
|
cellRect = table.getCellRect(row, cMin, false);
|
|
for(int column = cMin; column <= cMax; column++) {
|
|
aColumn = cm.getColumn(column);
|
|
columnWidth = aColumn.getWidth();
|
|
cellRect.width = columnWidth - columnMargin;
|
|
if (aColumn != draggedColumn) {
|
|
paintCell(g, cellRect, row, column);
|
|
}
|
|
cellRect.x += columnWidth;
|
|
}
|
|
}
|
|
} else {
|
|
for(int row = rMin; row <= rMax; row++) {
|
|
cellRect = table.getCellRect(row, cMin, false);
|
|
aColumn = cm.getColumn(cMin);
|
|
if (aColumn != draggedColumn) {
|
|
columnWidth = aColumn.getWidth();
|
|
cellRect.width = columnWidth - columnMargin;
|
|
paintCell(g, cellRect, row, cMin);
|
|
}
|
|
for(int column = cMin+1; column <= cMax; column++) {
|
|
aColumn = cm.getColumn(column);
|
|
columnWidth = aColumn.getWidth();
|
|
cellRect.width = columnWidth - columnMargin;
|
|
cellRect.x -= columnWidth;
|
|
if (aColumn != draggedColumn) {
|
|
paintCell(g, cellRect, row, column);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Paint the dragged column if we are dragging.
|
|
if (draggedColumn != null) {
|
|
paintDraggedArea(g, rMin, rMax, draggedColumn, header.getDraggedDistance());
|
|
}
|
|
|
|
// Remove any renderers that may be left in the rendererPane.
|
|
rendererPane.removeAll();
|
|
}
|
|
|
|
private void paintDraggedArea(Graphics g, int rMin, int rMax, TableColumn draggedColumn, int distance) {
|
|
int draggedColumnIndex = viewIndexForColumn(draggedColumn);
|
|
|
|
Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
|
|
Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);
|
|
|
|
Rectangle vacatedColumnRect = minCell.union(maxCell);
|
|
|
|
// Paint a gray well in place of the moving column.
|
|
g.setColor(table.getParent().getBackground());
|
|
g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
|
|
vacatedColumnRect.width, vacatedColumnRect.height);
|
|
|
|
// Move to the where the cell has been dragged.
|
|
vacatedColumnRect.x += distance;
|
|
|
|
// Fill the background.
|
|
g.setColor(table.getBackground());
|
|
g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
|
|
vacatedColumnRect.width, vacatedColumnRect.height);
|
|
|
|
// Paint the vertical grid lines if necessary.
|
|
if (table.getShowVerticalLines()) {
|
|
g.setColor(table.getGridColor());
|
|
int x1 = vacatedColumnRect.x;
|
|
int y1 = vacatedColumnRect.y;
|
|
int x2 = x1 + vacatedColumnRect.width - 1;
|
|
int y2 = y1 + vacatedColumnRect.height - 1;
|
|
// Left
|
|
g.drawLine(x1-1, y1, x1-1, y2);
|
|
// Right
|
|
g.drawLine(x2, y1, x2, y2);
|
|
}
|
|
|
|
for(int row = rMin; row <= rMax; row++) {
|
|
// Render the cell value
|
|
Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
|
|
r.x += distance;
|
|
paintCell(g, r, row, draggedColumnIndex);
|
|
|
|
// Paint the (lower) horizontal grid line if necessary.
|
|
if (table.getShowHorizontalLines()) {
|
|
g.setColor(table.getGridColor());
|
|
Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
|
|
rcr.x += distance;
|
|
int x1 = rcr.x;
|
|
int y1 = rcr.y;
|
|
int x2 = x1 + rcr.width - 1;
|
|
int y2 = y1 + rcr.height - 1;
|
|
g.drawLine(x1, y2, x2, y2);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
|
|
if (table.isEditing() && table.getEditingRow()==row &&
|
|
table.getEditingColumn()==column) {
|
|
Component component = table.getEditorComponent();
|
|
component.setBounds(cellRect);
|
|
component.validate();
|
|
}
|
|
else {
|
|
TableCellRenderer renderer = table.getCellRenderer(row, column);
|
|
Component component = table.prepareRenderer(renderer, row, column);
|
|
rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
|
|
cellRect.width, cellRect.height, true);
|
|
}
|
|
}
|
|
|
|
private static int getAdjustedLead(JTable table,
|
|
boolean row,
|
|
ListSelectionModel model) {
|
|
|
|
int index = model.getLeadSelectionIndex();
|
|
int compare = row ? table.getRowCount() : table.getColumnCount();
|
|
return index < compare ? index : -1;
|
|
}
|
|
|
|
private static int getAdjustedLead(JTable table, boolean row) {
|
|
return row ? getAdjustedLead(table, row, table.getSelectionModel())
|
|
: getAdjustedLead(table, row, table.getColumnModel().getSelectionModel());
|
|
}
|
|
|
|
|
|
private static final TransferHandler defaultTransferHandler = new TableTransferHandler();
|
|
|
|
static class TableTransferHandler extends TransferHandler implements UIResource {
|
|
|
|
/**
|
|
* Create a Transferable to use as the source for a data transfer.
|
|
*
|
|
* @param c The component holding the data to be transfered. This
|
|
* argument is provided to enable sharing of TransferHandlers by
|
|
* multiple components.
|
|
* @return The representation of the data to be transfered.
|
|
*
|
|
*/
|
|
protected Transferable createTransferable(JComponent c) {
|
|
if (c instanceof JTable) {
|
|
JTable table = (JTable) c;
|
|
int[] rows;
|
|
int[] cols;
|
|
|
|
if (!table.getRowSelectionAllowed() && !table.getColumnSelectionAllowed()) {
|
|
return null;
|
|
}
|
|
|
|
if (!table.getRowSelectionAllowed()) {
|
|
int rowCount = table.getRowCount();
|
|
|
|
rows = new int[rowCount];
|
|
for (int counter = 0; counter < rowCount; counter++) {
|
|
rows[counter] = counter;
|
|
}
|
|
} else {
|
|
rows = table.getSelectedRows();
|
|
}
|
|
|
|
if (!table.getColumnSelectionAllowed()) {
|
|
int colCount = table.getColumnCount();
|
|
|
|
cols = new int[colCount];
|
|
for (int counter = 0; counter < colCount; counter++) {
|
|
cols[counter] = counter;
|
|
}
|
|
} else {
|
|
cols = table.getSelectedColumns();
|
|
}
|
|
|
|
if (rows == null || cols == null || rows.length == 0 || cols.length == 0) {
|
|
return null;
|
|
}
|
|
|
|
StringBuffer plainBuf = new StringBuffer();
|
|
StringBuffer htmlBuf = new StringBuffer();
|
|
|
|
htmlBuf.append("<html>\n<body>\n<table>\n");
|
|
|
|
for (int row = 0; row < rows.length; row++) {
|
|
htmlBuf.append("<tr>\n");
|
|
for (int col = 0; col < cols.length; col++) {
|
|
Object obj = table.getValueAt(rows[row], cols[col]);
|
|
String val = ((obj == null) ? "" : obj.toString());
|
|
plainBuf.append(val + "\t");
|
|
htmlBuf.append(" <td>" + val + "</td>\n");
|
|
}
|
|
// we want a newline at the end of each line and not a tab
|
|
plainBuf.deleteCharAt(plainBuf.length() - 1).append("\n");
|
|
htmlBuf.append("</tr>\n");
|
|
}
|
|
|
|
// remove the last newline
|
|
plainBuf.deleteCharAt(plainBuf.length() - 1);
|
|
htmlBuf.append("</table>\n</body>\n</html>");
|
|
|
|
return new BasicTransferable(plainBuf.toString(), htmlBuf.toString());
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public int getSourceActions(JComponent c) {
|
|
return COPY;
|
|
}
|
|
|
|
}
|
|
} // End of Class BasicTableUI
|