blob: 865df79f2944b5d0a41380ea86648910bf068968 [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.refactoring.ui;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.actionSystem.DataSink;
import com.intellij.openapi.actionSystem.TypeSafeDataProvider;
import com.intellij.openapi.util.Iconable;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.classMembers.MemberInfoBase;
import com.intellij.refactoring.classMembers.MemberInfoChange;
import com.intellij.refactoring.classMembers.MemberInfoChangeListener;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.intellij.ui.*;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ui.EmptyIcon;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* @author Dennis.Ushakov
*/
public abstract class AbstractMemberSelectionTable<T extends PsiElement, M extends MemberInfoBase<T>> extends JBTable implements TypeSafeDataProvider {
protected static final int CHECKED_COLUMN = 0;
protected static final int DISPLAY_NAME_COLUMN = 1;
protected static final int ABSTRACT_COLUMN = 2;
protected static final Icon EMPTY_OVERRIDE_ICON = EmptyIcon.ICON_16;
protected static final String DISPLAY_NAME_COLUMN_HEADER = RefactoringBundle.message("member.column");
protected static final int OVERRIDE_ICON_POSITION = 2;
protected static final int VISIBILITY_ICON_POSITION = 1;
protected static final int MEMBER_ICON_POSITION = 0;
protected final String myAbstractColumnHeader;
protected List<M> myMemberInfos;
protected final boolean myAbstractEnabled;
protected MemberInfoModel<T, M> myMemberInfoModel;
protected MyTableModel<T, M> myTableModel;
public AbstractMemberSelectionTable(Collection<M> memberInfos, @Nullable MemberInfoModel<T, M> memberInfoModel, @Nullable String abstractColumnHeader) {
myAbstractEnabled = abstractColumnHeader != null;
myAbstractColumnHeader = abstractColumnHeader;
myTableModel = new MyTableModel<T, M>(this);
myMemberInfos = new ArrayList<M>(memberInfos);
if (memberInfoModel != null) {
myMemberInfoModel = memberInfoModel;
}
else {
myMemberInfoModel = new DefaultMemberInfoModel<T, M>();
}
setModel(myTableModel);
TableColumnModel model = getColumnModel();
model.getColumn(DISPLAY_NAME_COLUMN).setCellRenderer(new MyTableRenderer<T, M>(this));
TableColumn checkBoxColumn = model.getColumn(CHECKED_COLUMN);
TableUtil.setupCheckboxColumn(checkBoxColumn);
checkBoxColumn.setCellRenderer(new MyBooleanRenderer<T, M>(this));
if (myAbstractEnabled) {
int width = (int)(1.3 * getFontMetrics(getFont()).charsWidth(myAbstractColumnHeader.toCharArray(), 0, myAbstractColumnHeader.length()));
model.getColumn(ABSTRACT_COLUMN).setMaxWidth(width);
model.getColumn(ABSTRACT_COLUMN).setPreferredWidth(width);
model.getColumn(ABSTRACT_COLUMN).setCellRenderer(new MyBooleanRenderer<T, M>(this));
}
setPreferredScrollableViewportSize(new Dimension(400, getRowHeight() * 12));
getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
setShowGrid(false);
setIntercellSpacing(new Dimension(0, 0));
new MyEnableDisableAction().register();
new TableSpeedSearch(this);
}
public Collection<M> getSelectedMemberInfos() {
ArrayList<M> list = new ArrayList<M>(myMemberInfos.size());
for (M info : myMemberInfos) {
if (isMemberInfoSelected(info)) {
// if (info.isChecked() || (!myMemberInfoModel.isMemberEnabled(info) && myMemberInfoModel.isCheckedWhenDisabled(info))) {
list.add(info);
}
}
return list;
}
private boolean isMemberInfoSelected(final M info) {
final boolean memberEnabled = myMemberInfoModel.isMemberEnabled(info);
return (memberEnabled && info.isChecked()) || (!memberEnabled && myMemberInfoModel.isCheckedWhenDisabled(info));
}
public MemberInfoModel<T, M> getMemberInfoModel() {
return myMemberInfoModel;
}
public void setMemberInfoModel(MemberInfoModel<T, M> memberInfoModel) {
myMemberInfoModel = memberInfoModel;
}
public void fireExternalDataChange() {
myTableModel.fireTableDataChanged();
}
/**
* Redraws table
*/
public void redraw() {
myTableModel.redraw(getSelectedMemberInfos());
myTableModel.fireTableDataChanged();
}
public void setMemberInfos(Collection<M> memberInfos) {
myMemberInfos = new ArrayList<M>(memberInfos);
fireMemberInfoChange(memberInfos);
myTableModel.fireTableDataChanged();
}
public void addMemberInfoChangeListener(MemberInfoChangeListener<T, M> l) {
listenerList.add(MemberInfoChangeListener.class, l);
}
protected void fireMemberInfoChange(Collection<M> changedMembers) {
Object[] list = listenerList.getListenerList();
MemberInfoChange<T, M> event = new MemberInfoChange<T, M>(changedMembers);
for (Object element : list) {
if (element instanceof MemberInfoChangeListener) {
@SuppressWarnings("unchecked") final MemberInfoChangeListener<T, M> changeListener = (MemberInfoChangeListener<T, M>)element;
changeListener.memberInfoChanged(event);
}
}
}
@Override
public void calcData(final DataKey key, final DataSink sink) {
if (key == CommonDataKeys.PSI_ELEMENT) {
final Collection<M> memberInfos = getSelectedMemberInfos();
if (memberInfos.size() > 0) {
sink.put(CommonDataKeys.PSI_ELEMENT, memberInfos.iterator().next().getMember());
}
}
}
public void scrollSelectionInView() {
for(int i=0; i<myMemberInfos.size(); i++) {
if (isMemberInfoSelected(myMemberInfos.get(i))) {
Rectangle rc = getCellRect(i, 0, false);
scrollRectToVisible(rc);
break;
}
}
}
@Override
public void addNotify() {
super.addNotify();
scrollSelectionInView();
}
@Nullable
protected abstract Object getAbstractColumnValue(M memberInfo);
protected abstract boolean isAbstractColumnEditable(int rowIndex);
protected abstract void setVisibilityIcon(M memberInfo, RowIcon icon);
protected abstract Icon getOverrideIcon(M memberInfo);
private static class DefaultMemberInfoModel<T extends PsiElement, M extends MemberInfoBase<T>> implements MemberInfoModel<T, M> {
@Override
public boolean isMemberEnabled(M member) {
return true;
}
@Override
public boolean isCheckedWhenDisabled(M member) {
return false;
}
@Override
public boolean isAbstractEnabled(M member) {
return true;
}
@Override
public boolean isAbstractWhenDisabled(M member) {
return false;
}
@Override
public int checkForProblems(@NotNull M member) {
return OK;
}
@Override
public void memberInfoChanged(MemberInfoChange<T, M> event) {
}
@Override
public Boolean isFixedAbstract(M member) {
return null;
}
@Override
public String getTooltipText(M member) {
return null;
}
}
protected static class MyTableModel<T extends PsiElement, M extends MemberInfoBase<T>> extends AbstractTableModel {
private final AbstractMemberSelectionTable<T, M> myTable;
public MyTableModel(AbstractMemberSelectionTable<T, M> table) {
myTable = table;
}
@Override
public int getColumnCount() {
if (myTable.myAbstractEnabled) {
return 3;
}
else {
return 2;
}
}
@Override
public int getRowCount() {
return myTable.myMemberInfos.size();
}
@Override
public Class getColumnClass(int columnIndex) {
if (columnIndex == CHECKED_COLUMN || columnIndex == ABSTRACT_COLUMN) {
return Boolean.class;
}
return super.getColumnClass(columnIndex);
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
final M memberInfo = myTable.myMemberInfos.get(rowIndex);
switch (columnIndex) {
case CHECKED_COLUMN:
if (myTable.myMemberInfoModel.isMemberEnabled(memberInfo)) {
return memberInfo.isChecked() ? Boolean.TRUE : Boolean.FALSE;
}
else {
return myTable.myMemberInfoModel.isCheckedWhenDisabled(memberInfo);
}
case ABSTRACT_COLUMN:
{
return myTable.getAbstractColumnValue(memberInfo);
}
case DISPLAY_NAME_COLUMN:
return memberInfo.getDisplayName();
default:
throw new RuntimeException("Incorrect column index");
}
}
@Override
public String getColumnName(int column) {
switch (column) {
case CHECKED_COLUMN:
return " ";
case ABSTRACT_COLUMN:
return myTable.myAbstractColumnHeader;
case DISPLAY_NAME_COLUMN:
return DISPLAY_NAME_COLUMN_HEADER;
default:
throw new RuntimeException("Incorrect column index");
}
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
switch (columnIndex) {
case CHECKED_COLUMN:
return myTable.myMemberInfoModel.isMemberEnabled(myTable.myMemberInfos.get(rowIndex));
case ABSTRACT_COLUMN:
return myTable.isAbstractColumnEditable(rowIndex);
}
return false;
}
@Override
public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex) {
if (columnIndex == CHECKED_COLUMN) {
myTable.myMemberInfos.get(rowIndex).setChecked(((Boolean)aValue).booleanValue());
}
else if (columnIndex == ABSTRACT_COLUMN) {
myTable.myMemberInfos.get(rowIndex).setToAbstract(((Boolean)aValue).booleanValue());
}
Collection<M> changed = Collections.singletonList(myTable.myMemberInfos.get(rowIndex));
redraw(changed);
// fireTableRowsUpdated(rowIndex, rowIndex);
}
public void redraw(Collection<M> changed) {
myTable.fireMemberInfoChange(changed);
fireTableDataChanged();
}
}
private class MyEnableDisableAction extends EnableDisableAction {
@Override
protected JTable getTable() {
return AbstractMemberSelectionTable.this;
}
@Override
protected void applyValue(int[] rows, boolean valueToBeSet) {
List<M> changedInfo = new ArrayList<M>();
for (int row : rows) {
final M memberInfo = myMemberInfos.get(row);
memberInfo.setChecked(valueToBeSet);
changedInfo.add(memberInfo);
}
fireMemberInfoChange(changedInfo);
final int selectedRow = getSelectedRow();
myTableModel.fireTableDataChanged();
setRowSelectionInterval(selectedRow, selectedRow);
}
@Override
protected boolean isRowChecked(final int row) {
return myMemberInfos.get(row).isChecked();
}
}
private static class MyTableRenderer<T extends PsiElement, M extends MemberInfoBase<T>> extends ColoredTableCellRenderer {
private final AbstractMemberSelectionTable<T, M> myTable;
public MyTableRenderer(AbstractMemberSelectionTable<T, M> table) {
myTable = table;
}
@Override
public void customizeCellRenderer(JTable table, final Object value,
boolean isSelected, boolean hasFocus, final int row, final int column) {
final int modelColumn = myTable.convertColumnIndexToModel(column);
final M memberInfo = myTable.myMemberInfos.get(row);
setToolTipText(myTable.myMemberInfoModel.getTooltipText(memberInfo));
switch (modelColumn) {
case DISPLAY_NAME_COLUMN:
{
Icon memberIcon = myTable.getMemberIcon(memberInfo, 0);
Icon overrideIcon = myTable.getOverrideIcon(memberInfo);
RowIcon icon = new RowIcon(3);
icon.setIcon(memberIcon, MEMBER_ICON_POSITION);
myTable.setVisibilityIcon(memberInfo, icon);
icon.setIcon(overrideIcon, OVERRIDE_ICON_POSITION);
setIcon(icon);
break;
}
default:
{
setIcon(null);
}
}
setIconOpaque(false);
setOpaque(false);
final boolean cellEditable = myTable.myMemberInfoModel.isMemberEnabled(memberInfo);
setEnabled(cellEditable);
if (value == null) return;
final int problem = myTable.myMemberInfoModel.checkForProblems(memberInfo);
Color c = null;
if (problem == MemberInfoModel.ERROR) {
c = JBColor.RED;
}
else if (problem == MemberInfoModel.WARNING && !isSelected) {
c = JBColor.BLUE;
}
append((String)value, new SimpleTextAttributes(Font.PLAIN, c));
}
}
protected Icon getMemberIcon(M memberInfo, @Iconable.IconFlags int flags) {
return memberInfo.getMember().getIcon(flags);
}
private static class MyBooleanRenderer<T extends PsiElement, M extends MemberInfoBase<T>> extends BooleanTableCellRenderer {
private final AbstractMemberSelectionTable<T, M> myTable;
public MyBooleanRenderer(AbstractMemberSelectionTable<T, M> table) {
myTable = table;
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (component instanceof JCheckBox) {
int modelColumn = myTable.convertColumnIndexToModel(column);
M memberInfo = myTable.myMemberInfos.get(row);
component.setEnabled(
(modelColumn == CHECKED_COLUMN && myTable.myMemberInfoModel.isMemberEnabled(memberInfo)) ||
(modelColumn == ABSTRACT_COLUMN && memberInfo.isChecked() && myTable.isAbstractColumnEditable(row))
);
}
return component;
}
}
}