blob: fdcb69d54a4da277923663cfbbc9f5bbd7fb8d54 [file] [log] [blame]
/*
* Copyright 2000-2012 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;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.application.ApplicationBundle;
import com.intellij.openapi.util.NullableComputable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.AnActionButton;
import com.intellij.ui.AnActionButtonRunnable;
import com.intellij.ui.HoverHyperlinkLabel;
import com.intellij.ui.ToolbarDecorator;
import com.intellij.ui.table.TableView;
import com.intellij.util.IconUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public abstract class ValidatingTableEditor<Item> implements ComponentWithEmptyText {
private static final Icon WARNING_ICON = UIUtil.getBalloonWarningIcon();
private static final Icon EMPTY_ICON = EmptyIcon.create(WARNING_ICON);
@NonNls private static final String REMOVE_KEY = "REMOVE_SELECTED";
public interface RowHeightProvider {
int getRowHeight();
}
public interface Fix extends Runnable {
String getTitle();
}
private class ColumnInfoWrapper extends ColumnInfo<Item, Object> {
private final ColumnInfo<Item, Object> myDelegate;
public ColumnInfoWrapper(ColumnInfo<Item, Object> delegate) {
super(delegate.getName());
myDelegate = delegate;
}
@Override
public Object valueOf(Item item) {
return myDelegate.valueOf(item);
}
@Override
public boolean isCellEditable(Item item) {
return myDelegate.isCellEditable(item);
}
@Override
public void setValue(Item item, Object value) {
myDelegate.setValue(item, value);
updateMessage(-1, null);
}
@Override
public TableCellEditor getEditor(Item item) {
return myDelegate.getEditor(item);
}
@Override
public int getWidth(JTable table) {
return myDelegate.getWidth(table);
}
@Override
public Class getColumnClass() {
return myDelegate.getColumnClass();
}
@Override
public TableCellRenderer getRenderer(Item item) {
return myDelegate.getRenderer(item);
}
}
private JPanel myContentPane;
private TableView<Item> myTable;
private AnActionButton myRemoveButton;
private JLabel myMessageLabel;
private HoverHyperlinkLabel myFixLink;
private JPanel myTablePanel;
private final List<String> myWarnings = new ArrayList<String>();
private Fix myFixRunnable;
protected abstract Item cloneOf(Item item);
@Nullable
protected Pair<String, Fix> validate(List<Item> current, List<String> warnings) {
String error = null;
for (int i = 0; i < current.size(); i++) {
Item item = current.get(i);
String s = validate(item);
warnings.set(i, s);
if (error == null) {
error = s;
}
}
return error != null ? Pair.create(error, (Fix)null) : null;
}
@Nullable
protected String validate(Item item) {
return null;
}
@Nullable
protected abstract Item createItem();
private class IconColumn extends ColumnInfo<Item, Object> implements RowHeightProvider {
public IconColumn() {
super(" ");
}
public String valueOf(Item item) {
return null;
}
@Override
public int getWidth(JTable table) {
return WARNING_ICON.getIconWidth() + 2;
}
public int getRowHeight() {
return WARNING_ICON.getIconHeight();
}
@Override
public TableCellRenderer getRenderer(final Item item) {
return new WarningIconCellRenderer(new NullableComputable<String>() {
public String compute() {
return myWarnings.get(doGetItems().indexOf(item));
}
});
}
}
@NotNull
@Override
public StatusText getEmptyText() {
return myTable.getEmptyText();
}
private void createUIComponents() {
myTable = new ChangesTrackingTableView<Item>() {
protected void onCellValueChanged(int row, int column, Object value) {
final Item original = getItems().get(row);
Item override = cloneOf(original);
final ColumnInfo<Item, Object> columnInfo = getTableModel().getColumnInfos()[column];
columnInfo.setValue(override, value);
updateMessage(row, override);
}
@Override
protected void onEditingStopped() {
updateMessage(-1, null);
}
};
myFixLink = new HoverHyperlinkLabel(null);
}
protected ValidatingTableEditor(@Nullable AnActionButton ... extraButtons) {
ToolbarDecorator decorator =
ToolbarDecorator.createDecorator(myTable).disableRemoveAction().disableUpAction().disableDownAction();
decorator.setAddAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton anActionButton) {
addItem();
}
});
myRemoveButton = new AnActionButton(ApplicationBundle.message("button.remove"), IconUtil.getRemoveIcon()) {
@Override
public void actionPerformed(AnActionEvent e) {
removeSelected();
}
};
myRemoveButton.setShortcut(CustomShortcutSet.fromString("alt DELETE")); //NON-NLS
decorator.addExtraAction(myRemoveButton);
if (extraButtons != null && extraButtons.length != 0) {
for (AnActionButton extraButton : extraButtons) {
decorator.addExtraAction(extraButton);
}
}
myTablePanel.add(decorator.createPanel(), BorderLayout.CENTER);
myTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
updateButtons();
}
});
myTable.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), REMOVE_KEY);
myTable.getActionMap().put(REMOVE_KEY, new AbstractAction() {
public void actionPerformed(final ActionEvent e) {
removeSelected();
}
});
myFixLink.addHyperlinkListener(new HyperlinkListener() {
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED && myFixRunnable != null) {
myFixRunnable.run();
}
}
});
}
protected ValidatingTableEditor() {
//noinspection NullArgumentToVariableArgMethod
this(null);
}
@Nullable
public List<Item> getSelectedItems() {
return myTable.getSelectedObjects();
}
private void removeSelected() {
myTable.stopEditing();
List<Item> items = new ArrayList<Item>(doGetItems());
final int[] rows = myTable.getSelectedRows();
for (int i = rows.length - 1; i >= 0; i--) {
items.remove(rows[i]);
}
setItems(items);
updateMessage(-1, null);
if (!items.isEmpty()) {
int index = Math.min(rows[0], items.size() - 1);
myTable.getSelectionModel().addSelectionInterval(index, index);
}
}
protected void addItem() {
Item newItem = createItem();
if (newItem == null) {
return;
}
List<Item> items = new ArrayList<Item>(doGetItems());
items.add(newItem);
setItems(items);
final int row = items.size() - 1;
myTable.getSelectionModel().setSelectionInterval(row, row);
myTable.scrollRectToVisible(myTable.getCellRect(row, 0, true));
if (getTableModel().getColumnInfos()[1].isCellEditable(items.get(row))) {
myTable.editCellAt(row, 1);
IdeFocusManager.findInstanceByComponent(myContentPane).requestFocus(myTable.getEditorComponent(), true);
}
updateMessage(-1, null);
}
private ListTableModel<Item> getTableModel() {
return (ListTableModel<Item>)myTable.getModel();
}
public void setModel(ColumnInfo<Item, Object>[] valueColumns, List<Item> items) {
ColumnInfo[] columns = new ColumnInfo[valueColumns.length + 1];
IconColumn iconColumn = new IconColumn();
int maxHeight = iconColumn.getRowHeight();
columns[0] = iconColumn;
for (int i = 0; i < valueColumns.length; i++) {
columns[i + 1] = new ColumnInfoWrapper(valueColumns[i]);
if (valueColumns[i] instanceof RowHeightProvider) {
maxHeight = Math.max(maxHeight, ((RowHeightProvider)valueColumns[i]).getRowHeight());
}
}
myTable.stopEditing();
myTable.setModelAndUpdateColumns(new ListTableModel<Item>(columns));
if (maxHeight > 0) {
myTable.setRowHeight(maxHeight);
}
setItems(items);
updateMessage(-1, null);
}
public List<Item> getItems() {
return Collections.unmodifiableList(doGetItems());
}
private List<Item> doGetItems() {
List<Item> items = new ArrayList<Item>(getTableModel().getItems());
if (myTable.isEditing()) {
Object value = ChangesTrackingTableView.getValue(myTable.getEditorComponent());
ColumnInfo column = ((ListTableModel)myTable.getModel()).getColumnInfos()[myTable.getEditingColumn()];
((ColumnInfoWrapper)column).myDelegate.setValue(items.get(myTable.getEditingRow()), value);
}
return items;
}
private void setItems(List<Item> items) {
if (items.isEmpty()) {
getTableModel().setItems(Collections.<Item>emptyList());
myWarnings.clear();
}
else {
myWarnings.clear();
for (Item item : items) {
myWarnings.add(null);
}
getTableModel().setItems(new ArrayList<Item>(items));
}
updateButtons();
}
public void setTableHeader(JTableHeader header) {
myTable.setTableHeader(header);
}
private void updateButtons() {
myRemoveButton.setEnabled(myTable.getSelectedRow() != -1);
}
public void updateMessage(int index, @Nullable Item override) {
List<Item> current = new ArrayList<Item>(doGetItems());
if (override != null) {
current.set(index, override);
}
displayMessageAndFix(validate(current, myWarnings));
myTable.repaint();
}
protected void displayMessageAndFix(@Nullable Pair<String, Fix> messageAndFix) {
if (messageAndFix != null) {
myMessageLabel.setText(messageAndFix.first);
myMessageLabel.setIcon(WARNING_ICON);
myMessageLabel.setVisible(true);
myFixRunnable = messageAndFix.second;
myFixLink.setVisible(myFixRunnable != null);
myFixLink.setText(myFixRunnable != null ? myFixRunnable.getTitle() : null);
}
else {
myMessageLabel.setText(" ");
myMessageLabel.setIcon(EMPTY_ICON);
myFixLink.setVisible(false);
myFixRunnable = null;
}
}
public void hideMessageLabel() {
myMessageLabel.setVisible(false);
myFixLink.setVisible(false);
}
public JComponent getPreferredFocusedComponent() {
return myTable;
}
private static class WarningIconCellRenderer extends DefaultTableCellRenderer {
private final NullableComputable<String> myWarningProvider;
public WarningIconCellRenderer(NullableComputable<String> warningProvider) {
myWarningProvider = warningProvider;
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
String message = myWarningProvider.compute();
label.setIcon(message != null ? WARNING_ICON : null);
label.setToolTipText(message);
label.setHorizontalAlignment(CENTER);
label.setVerticalAlignment(CENTER);
return label;
}
}
public Component getContentPane() {
return myContentPane;
}
public void setColumnReorderingAllowed(boolean value) {
JTableHeader header = myTable.getTableHeader();
if (header != null) {
header.setReorderingAllowed(value);
}
}
}