blob: 4ab92f15293bc34574c32839bf122b907e7d2ac0 [file] [log] [blame]
/*
* 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.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.Ref;
import com.intellij.ui.*;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ui.AbstractTableCellEditor;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.List;
import static java.awt.event.KeyEvent.*;
/**
* @author Konstantin Bulenkov
*/
public abstract class JBListTable extends JPanel {
protected final JTable myInternalTable;
private final JBTable mainTable;
private final Ref<Integer> myLastEditorIndex = Ref.create(null);
private MouseEvent myMouseEvent;
private MyCellEditor myCellEditor;
public JBListTable(@NotNull final JTable t) {
super(new BorderLayout());
myInternalTable = t;
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() {
myLastEditorIndex.set(null);
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) {
return new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int col) {
return getRowRenderer(t, row, selected, hasFocus);
}
};
}
@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 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);
}
};
mainTable.setStriped(true);
}
public void stopEditing() {
TableUtil.stopEditing(mainTable);
}
private static void installPaddingAndBordersForEditors(JBTableRowEditor editor) {
final List<EditorTextField> 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 JComponent getRowRenderer(JTable table, int row, boolean selected, boolean focused);
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 static class RowResizeAnimator extends Thread {
private final JTable myTable;
private final int myRow;
private final JScrollPane myScrollPane;
private int neededHeight;
private final JBTableRowEditor myEditor;
private final Ref<Integer> myIndex;
private int step = 5;
private int currentHeight;
private RowResizeAnimator(JTable table, int row, int height, JBTableRowEditor editor, @NotNull Ref<Integer> index) {
super("Row Animator");
myTable = table;
myRow = row;
neededHeight = height;
myEditor = editor;
myIndex = index;
currentHeight = myTable.getRowHeight(myRow);
myScrollPane = UIUtil.getParentOfType(JScrollPane.class, myTable);
}
@Override
public void run() {
final boolean exitEditing = currentHeight > neededHeight;
try {
sleep(50);
final JScrollBar bar = myScrollPane.getVerticalScrollBar();
if (bar == null || !bar.isVisible()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
}
});
sleep(15);
}
while (currentHeight != neededHeight) {
if (Math.abs(currentHeight - neededHeight) < step) {
currentHeight = neededHeight;
}
else {
currentHeight += currentHeight < neededHeight ? step : -step;
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myTable.setRowHeight(myRow, currentHeight);
}
});
sleep(15);
}
if (myEditor != null) {
JComponent[] components = myEditor.getFocusableComponents();
JComponent focus = null;
if (myIndex.get() != null) {
int index = myIndex.get().intValue();
if (0 <= index && index < components.length) {
focus = components[index];
}
}
if (focus == null) {
focus = myEditor.getPreferredFocusedComponent();
}
if (focus != null) {
focus.requestFocus();
}
}
}
catch (InterruptedException ignore) {
} finally {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TableUtil.scrollSelectionToVisible(myTable);
if (exitEditing && !myTable.isEditing()) {
myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
}
}
});
}
}
}
private class MyCellEditor extends AbstractTableCellEditor implements Animated {
JTable curTable;
private final JBTableRowEditor myEditor;
public MyCellEditor(JBTableRowEditor editor) {
myEditor = editor;
curTable = null;
}
@Override
public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected, final int row, int column) {
curTable = table;
final JPanel p = new JPanel(new BorderLayout()) {
@Override
public void addNotify() {
super.addNotify();
final int height = (int)getPreferredSize().getHeight();
if (height > table.getRowHeight(row)) {
new RowResizeAnimator(table, row, height, myEditor, myMouseEvent == null ? myLastEditorIndex : Ref.<Integer>create(null)).start();
}
}
public void removeNotify() {
if (myCellEditor != null) myCellEditor.saveFocusIndex();
super.removeNotify();
new RowResizeAnimator(table, row, table.getRowHeight(), null, myMouseEvent == null ? myLastEditorIndex : Ref.<Integer>create(null)).start();
}
};
p.add(myEditor, BorderLayout.CENTER);
p.setFocusable(false);
return p;
}
@Override
public Object getCellEditorValue() {
return myEditor.getValue();
}
@Override
public boolean stopCellEditing() {
saveFocusIndex();
return super.stopCellEditing();
}
private void removeEmptyRow() {
final int row = curTable.getSelectedRow();
if (row != -1 && isRowEmpty(row)) {
final JBListTableModel model = (JBListTableModel)curTable.getModel();
final int count = model.getRowCount();
model.removeRow(row);
int newRow = count == row + 1 ? row - 1 : row;
curTable.setRowSelectionInterval(newRow, newRow);
}
}
public void saveFocusIndex() {
JComponent[] components = myEditor.getFocusableComponents();
for (int i = 0; i < components.length; i++) {
if (components[i].hasFocus()) {
JBListTable.this.myLastEditorIndex.set(i);
break;
}
}
}
@Override
public void cancelCellEditing() {
saveFocusIndex();
super.cancelCellEditing();
}
}
}