Collection<M> () { ArrayList<M> list = new ArrayList<>(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<>(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<>(changedMembers); for (Object element : list) { if (element instanceof MemberInfoChangeListener) { @SuppressWarnings("unchecked") final MemberInfoChangeListener<T, M> changeListener = (MemberInfoChangeListener<T, M>)element; changeListener.memberInfoChanged(event); } } } @Nullable @Override public Object getData(@NotNull String dataId) { if (PlatformCoreDataKeys.BGT_DATA_PROVIDER.is(dataId)) { M item = ContainerUtil.getFirstItem(getSelectedMemberInfos()); if (item == null) return null; return (DataProvider)slowId -> getSlowData(slowId, item); } return null; } private @Nullable Object getSlowData(@NotNull String dataId, @NotNull M item) { if (CommonDataKeys.PSI_ELEMENT.is(dataId)) { return item.getMember(); } return null; } 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(); } @Override public void removeNotify() { super.removeNotify(); myCancellablePromise.cancel(); } @Nullable protected abstract Object getAbstractColumnValue(M memberInfo); protected abstract boolean isAbstractColumnEditable(int rowIndex); protected void setVisibilityIcon(M memberInfo, com.intellij.ui.RowIcon icon) { throw new AbstractMethodError(); } protected void setVisibilityIcon(M memberInfo, RowIcon icon) { setVisibilityIcon(memberInfo, (com.intellij.ui.RowIcon)icon); } protected abstract Icon getOverrideIcon(M memberInfo); private static final 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(@NotNull MemberInfoChange<T, M> event) { } @Override public Boolean isFixedAbstract(M member) { return null; } @Override public String getTooltipText(M member) { return null; } } protected static final 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); return switch (columnIndex) { case CHECKED_COLUMN -> { if (myTable.myMemberInfoModel.isMemberEnabled(memberInfo)) { yield memberInfo.isChecked(); } else { yield myTable.myMemberInfoModel.isCheckedWhenDisabled(memberInfo); } } case ABSTRACT_COLUMN -> myTable.getAbstractColumnValue(memberInfo); case DISPLAY_NAME_COLUMN -> memberInfo.getDisplayName(); default -> throw new RuntimeException("Incorrect column index"); }; } @Override public String getColumnName(int column) { return switch (column) { case CHECKED_COLUMN -> " "; case ABSTRACT_COLUMN -> myTable.myAbstractColumnHeader; case DISPLAY_NAME_COLUMN -> getDisplayNameColumnHeader(); default -> throw new RuntimeException("Incorrect column index"); }; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return switch (columnIndex) { case CHECKED_COLUMN -> myTable.myMemberInfoModel.isMemberEnabled(myTable.myMemberInfos.get(rowIndex)); case ABSTRACT_COLUMN -> myTable.isAbstractColumnEditable(rowIndex); default -> 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 final class MyEnableDisableAction extends EnableDisableAction { @Override protected JTable getTable() { return AbstractMemberSelectionTable.this; } @Override protected void applyValue(int[] rows, boolean valueToBeSet) { List<M> changedInfo = new ArrayList<>(); for (int row : rows) { final M memberInfo = myMemberInfos.get(row); memberInfo.setChecked(valueToBeSet); changedInfo.add(memberInfo); } fireMemberInfoChange(changedInfo); final int[] selectedRows = getSelectedRows(); myTableModel.fireTableDataChanged(); final ListSelectionModel selectionModel = getSelectionModel(); for (int selectedRow : selectedRows) { selectionModel.addSelectionInterval(selectedRow, selectedRow); } } @Override protected boolean isRowChecked(final int row) { return myMemberInfos.get(row).isChecked(); } } protected Icon getMemberIcon(M memberInfo, @Iconable.IconFlags int flags) { return memberInfo.getMember().getIcon(flags); } private record MemberInfoData(@Nls String tooltip, Icon icon, boolean isEditable, int problem) {} private static final class MyTableRenderer<T extends PsiElement, M extends MemberInfoBase<T>> extends ColoredTableCellRenderer { private final AbstractMemberSelectionTable<T, M> myTable; MyTableRenderer(AbstractMemberSelectionTable<T, M> table) { myTable = table; } @Override public void customizeCellRenderer(@NotNull JTable table, final Object value, boolean isSelected, boolean hasFocus, final int row, final int column) { setIconOpaque(false); setOpaque(false); removeAll(); MemberInfoData data = myTable.getMemberInfoData(row); if (data == null) { append(IdeBundle.message("progress.text.loading")); setIcon(AnimatedIcon.Default.INSTANCE); return; } setToolTipText(data.tooltip()); setIcon(data.icon()); setEnabled(data.isEditable()); if (value instanceof @NlsSafe String s) append(s); } } private static final class MyBooleanRenderer<T extends PsiElement, M extends MemberInfoBase<T>> extends BooleanTableCellRenderer { private final AbstractMemberSelectionTable<T, M> myTable; 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; } } protected static @NlsContexts.ColumnName String getDisplayNameColumnHeader() { return RefactoringBundle.message("member.column"); } }