com.intellij.util.ui.table.JBListTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of platform-impl Show documentation
Show all versions of platform-impl Show documentation
A packaging of the IntelliJ Community Edition platform-impl library.
This is release number 1 of trunk branch 142.
The newest version!
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.ui.table;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorFontType;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.DottedBorder;
import com.intellij.ui.EditorSettingsProvider;
import com.intellij.ui.EditorTextField;
import com.intellij.ui.TableUtil;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ui.AbstractTableCellEditor;
import com.intellij.util.ui.UIUtil;
import gnu.trove.TIntArrayList;
import gnu.trove.TIntObjectHashMap;
import gnu.trove.TIntObjectProcedure;
import gnu.trove.TIntProcedure;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import static java.awt.event.KeyEvent.*;
/**
* @author Konstantin Bulenkov
*/
public abstract class JBListTable {
protected final JTable myInternalTable;
private final JBTable mainTable;
private final RowResizeAnimator myRowResizeAnimator;
private final Disposable myOnRemoveDisposable;
private MouseEvent myMouseEvent;
private MyCellEditor myCellEditor;
private int myLastFocusedEditorComponentIdx = -1;
public JBListTable(@NotNull JTable t) {
this(t, Disposer.get("ui"));
}
public JBListTable(@NotNull final JTable t, @NotNull Disposable parent) {
myInternalTable = t;
myOnRemoveDisposable = Disposer.newDisposable();
Disposer.register(parent, myOnRemoveDisposable);
final JBListTableModel model = new JBListTableModel(t.getModel()) {
@Override
public JBTableRow getRow(int index) {
return getRowAt(index);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return isRowEditable(rowIndex);
}
@Override
public void addRow() {
myLastFocusedEditorComponentIdx = -1;
super.addRow();
}
};
mainTable = new JBTable(model) {
@Override
public void editingStopped(ChangeEvent e) {
super.editingStopped(e);
}
@Override
public void editingCanceled(ChangeEvent e) {
super.editingCanceled(e);
}
@Override
protected void processKeyEvent(KeyEvent e) {
myMouseEvent = null;
//Mnemonics
if (e.isAltDown()) {
super.processKeyEvent(e);
return;
}
if (e.getKeyCode() == VK_TAB) {
if (e.getID() == KEY_PRESSED) {
final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
if (e.isShiftDown()) {
keyboardFocusManager.focusPreviousComponent(this);
}
else {
keyboardFocusManager.focusNextComponent(this);
}
}
e.consume();
return;
}
super.processKeyEvent(e);
}
@Override
protected void processMouseEvent(MouseEvent e) {
myMouseEvent = e;
super.processMouseEvent(e);
}
@Override
public TableCellRenderer getCellRenderer(int row, int column) {
final JBTableRowRenderer rowRenderer = getRowRenderer(row);
return new TableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focused, int row, int col) {
return rowRenderer.getRowRendererComponent(t, row, selected, focused);
}
};
}
@Override
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
//Mnemonics and actions
if (e.isAltDown() || e.isMetaDown() || e.isControlDown()) {
return false;
}
if (e.getKeyCode() == VK_ESCAPE && pressed) {
final int row = getSelectedRow();
if (row != -1 && isRowEmpty(row)) {
final int count = model.getRowCount();
model.removeRow(row);
int newRow = count == row + 1 ? row - 1 : row;
if (0 <= newRow && newRow < model.getRowCount()) {
setRowSelectionInterval(newRow, newRow);
}
}
}
if (e.getKeyCode() == VK_ENTER) {
if (e.getID() == KEY_PRESSED) {
if (!isEditing() && e.getModifiers() == 0) {
editCellAt(getSelectedRow(), getSelectedColumn());
}
else if (isEditing()) {
TableUtil.stopEditing(this);
if (e.isControlDown() || e.isMetaDown()) {
return false;
}
else {
final int row = getSelectedRow() + 1;
if (row < getRowCount()) {
getSelectionModel().setSelectionInterval(row, row);
}
}
}
else {
if (e.isControlDown() || e.isMetaDown()) {
return false;
}
}
}
e.consume();
return true;
}
if (isEditing() && e.getKeyCode() == VK_TAB) {
if (pressed) {
final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager();
if (e.isShiftDown()) {
mgr.focusPreviousComponent();
}
else {
mgr.focusNextComponent();
}
}
return true;
}
final boolean isUp = e.getKeyCode() == VK_UP;
final boolean isDown = e.getKeyCode() == VK_DOWN;
if (isEditing() && (isUp || isDown) && e.getModifiers() == 0 && e.getID() == KEY_PRESSED) {
int row = getSelectedRow();
super.processKeyBinding(ks, e, condition, pressed);
if (!isEditing() && row != getSelectedRow()) {
TableUtil.editCellAt(this, getSelectedRow(), 0);
e.consume();
return true;
}
}
return super.processKeyBinding(ks, e, condition, pressed);
}
@Override
public void columnMarginChanged(ChangeEvent e) {
// we don't stop editing (it prevents editor removal when scrollbar is added)
TableColumn resizingColumn = tableHeader != null ? tableHeader.getResizingColumn() : null;
if (resizingColumn != null && autoResizeMode == AUTO_RESIZE_OFF) {
resizingColumn.setPreferredWidth(resizingColumn.getWidth());
}
resizeAndRepaint();
}
@Override
public TableCellEditor getCellEditor(final int row, int column) {
final JBTableRowEditor editor = getRowEditor(row);
if (editor != null) {
editor.setMouseEvent(myMouseEvent);
editor.prepareEditor(t, row);
installPaddingAndBordersForEditors(editor);
editor.setFocusCycleRoot(true);
editor.setFocusTraversalPolicy(new JBListTableFocusTraversalPolicy(editor));
MouseSuppressor.install(editor);
myCellEditor = new MyCellEditor(editor);
return myCellEditor;
}
myCellEditor = null;
return myCellEditor;
}
@Override
public Component prepareEditor(TableCellEditor editor, int row, int column) {
Object value = getValueAt(row, column);
boolean isSelected = isCellSelected(row, column);
return editor.getTableCellEditorComponent(this, value, isSelected, row, column);
}
@Override
public void addNotify() {
super.addNotify();
Disposer.register(myOnRemoveDisposable, myRowResizeAnimator);
}
@Override
public void removeNotify() {
super.removeNotify();
Disposer.dispose(myOnRemoveDisposable);
}
};
mainTable.setStriped(true);
myRowResizeAnimator = new RowResizeAnimator(mainTable);
}
public void stopEditing() {
TableUtil.stopEditing(mainTable);
}
public Disposable getOnRemoveDisposable() {
return myOnRemoveDisposable;
}
private static void installPaddingAndBordersForEditors(JBTableRowEditor editor) {
final List editors = UIUtil.findComponentsOfType(editor, EditorTextField.class);
for (EditorTextField textField : editors) {
textField.putClientProperty("JComboBox.isTableCellEditor", Boolean.FALSE);
textField.putClientProperty("JBListTable.isTableCellEditor", Boolean.TRUE);
}
}
public final JBTable getTable() {
return mainTable;
}
protected abstract JBTableRowRenderer getRowRenderer(int row);
protected abstract JBTableRowEditor getRowEditor(int row);
protected JBTableRow getRowAt(final int row) {
return new JBTableRow() {
@Override
public Object getValueAt(int column) {
return myInternalTable.getValueAt(row, column);
}
};
}
protected boolean isRowEditable(int row) {
return true;
}
protected boolean isRowEmpty(int row) {
return false;
}
public static JComponent createEditorTextFieldPresentation(final Project project,
final FileType type,
final String text,
boolean selected,
boolean focused) {
final JPanel panel = new JPanel(new BorderLayout());
final EditorTextField field = new EditorTextField(text, project, type) {
@Override
protected boolean shouldHaveBorder() {
return false;
}
};
Font font = EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.PLAIN);
font = new Font(font.getFontName(), font.getStyle(), 12);
field.setFont(font);
field.addSettingsProvider(EditorSettingsProvider.NO_WHITESPACE);
if (selected && focused) {
panel.setBackground(UIUtil.getTableSelectionBackground());
field.setAsRendererWithSelection(UIUtil.getTableSelectionBackground(), UIUtil.getTableSelectionForeground());
} else {
panel.setBackground(UIUtil.getTableBackground());
if (selected) {
panel.setBorder(new DottedBorder(UIUtil.getTableForeground()));
}
}
panel.add(field, BorderLayout.WEST);
return panel;
}
private class MyCellEditor extends AbstractTableCellEditor {
private final JBTableRowEditor myEditor;
public MyCellEditor(JBTableRowEditor editor) {
myEditor = editor;
}
@Override
public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected, final int row, int column) {
final JPanel p = new JPanel(new BorderLayout()) {
@Override
public void addNotify() {
super.addNotify();
int height = getPreferredSize().height;
if (height > table.getRowHeight(row)) {
myRowResizeAnimator.resize(row, height);
}
}
public void removeNotify() {
if (myCellEditor != null) myCellEditor.saveFocusIndex();
super.removeNotify();
myRowResizeAnimator.resize(row, table.getRowHeight());
}
};
p.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
IdeFocusManager focusManager = IdeFocusManager.findInstanceByComponent(p);
focusManager.requestFocus(getComponentToFocus(), true);
}
private Component getComponentToFocus() {
if (myLastFocusedEditorComponentIdx >= 0) {
JComponent[] focusableComponents = myEditor.getFocusableComponents();
if (myLastFocusedEditorComponentIdx < focusableComponents.length) {
return focusableComponents[myLastFocusedEditorComponentIdx];
}
}
return myEditor.getPreferredFocusedComponent();
}
});
p.add(myEditor, BorderLayout.CENTER);
return p;
}
@Override
public Object getCellEditorValue() {
return myEditor.getValue();
}
@Override
public boolean stopCellEditing() {
saveFocusIndex();
return super.stopCellEditing();
}
@Override
public void cancelCellEditing() {
saveFocusIndex();
super.cancelCellEditing();
}
private void saveFocusIndex() {
JComponent[] components = myEditor.getFocusableComponents();
for (int i = 0; i < components.length; i++) {
if (components[i].hasFocus()) {
myLastFocusedEditorComponentIdx = i;
break;
}
}
}
}
private static class RowResizeAnimator implements ActionListener, Disposable {
private static final int ANIMATION_STEP_MILLIS = 15;
private static final int RESIZE_AMOUNT_PER_STEP = 5;
private final TIntObjectHashMap myRowAnimationStates = new TIntObjectHashMap();
private final Timer myAnimationTimer = new Timer(ANIMATION_STEP_MILLIS, this);
private final JTable myTable;
public RowResizeAnimator(JTable table) {
myTable = table;
}
public void resize(int row, int targetHeight) {
myRowAnimationStates.put(row, new RowAnimationState(row, targetHeight));
startAnimation();
}
@Override
public void actionPerformed(final ActionEvent e) {
doAnimationStep(e.getWhen());
}
@Override
public void dispose() {
stopAnimation();
}
private void startAnimation() {
if (!myAnimationTimer.isRunning()) {
myAnimationTimer.start();
}
}
private void stopAnimation() {
myAnimationTimer.stop();
}
private void doAnimationStep(final long updateTime) {
final TIntArrayList completeRows = new TIntArrayList(myRowAnimationStates.size());
myRowAnimationStates.forEachEntry(new TIntObjectProcedure() {
@Override
public boolean execute(int row, RowAnimationState animationState) {
if (animationState.doAnimationStep(updateTime)) {
completeRows.add(row);
}
return true;
}
});
completeRows.forEach(new TIntProcedure() {
@Override
public boolean execute(int row) {
myRowAnimationStates.remove(row);
return true;
}
});
if (myRowAnimationStates.isEmpty()) {
stopAnimation();
}
}
private class RowAnimationState {
private final int myRow;
private final int myTargetHeight;
private long myLastUpdateTime;
public RowAnimationState(int row, int targetHeight) {
myRow = row;
myTargetHeight = targetHeight;
myLastUpdateTime = System.currentTimeMillis();
}
/**
* @return whether this row animation is complete
*/
public boolean doAnimationStep(long currentTime) {
if (myRow >= myTable.getRowCount()) return true;
int currentRowHeight = myTable.getRowHeight(myRow);
int resizeAbs = (int) (RESIZE_AMOUNT_PER_STEP * ((currentTime - myLastUpdateTime) / (double)ANIMATION_STEP_MILLIS));
int leftToAnimate = myTargetHeight - currentRowHeight;
int newHeight = Math.abs(leftToAnimate) <= resizeAbs ? myTargetHeight :
currentRowHeight + (leftToAnimate < 0 ? -resizeAbs : resizeAbs);
myTable.setRowHeight(myRow, newHeight);
myLastUpdateTime = currentTime;
return myTargetHeight == newHeight;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy