diff options
Diffstat (limited to 'platform/platform-impl/src/com/intellij/ide/util')
-rw-r--r-- | platform/platform-impl/src/com/intellij/ide/util/ElementsChooser.java | 611 | ||||
-rw-r--r-- | platform/platform-impl/src/com/intellij/ide/util/MultiStateElementsChooser.java | 692 |
2 files changed, 763 insertions, 540 deletions
diff --git a/platform/platform-impl/src/com/intellij/ide/util/ElementsChooser.java b/platform/platform-impl/src/com/intellij/ide/util/ElementsChooser.java index 2b8a8a499621..fcbe27f5c4a5 100644 --- a/platform/platform-impl/src/com/intellij/ide/util/ElementsChooser.java +++ b/platform/platform-impl/src/com/intellij/ide/util/ElementsChooser.java @@ -15,383 +15,89 @@ */ package com.intellij.ide.util; -import com.intellij.ui.*; -import com.intellij.ui.table.JBTable; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.ui.ComponentWithEmptyText; -import com.intellij.util.ui.StatusText; -import com.intellij.util.ui.Table; -import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; -import javax.swing.event.ListSelectionListener; -import javax.swing.table.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.util.*; +import javax.swing.table.TableCellRenderer; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Map; /** * @see ChooseElementsDialog */ -public class ElementsChooser<T> extends JPanel implements ComponentWithEmptyText, ComponentWithExpandableItems<TableCell> { - private JBTable myTable = null; - private MyTableModel myTableModel = null; - private boolean myColorUnmarkedElements = true; - private final List<ElementsMarkListener<T>> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); - private final Map<T,ElementProperties> myElementToPropertiesMap = new HashMap<T, ElementProperties>(); - private final Map<T, Boolean> myDisabledMap = new HashMap<T, Boolean>(); +public class ElementsChooser<T> extends MultiStateElementsChooser<T, Boolean> { + private static final BooleanMarkStateDescriptor MARK_STATE_DESCRIPTOR = new BooleanMarkStateDescriptor(); public interface ElementsMarkListener<T> { void elementMarkChanged(T element, boolean isMarked); } public ElementsChooser(final boolean elementsCanBeMarked) { - this(null, false, elementsCanBeMarked); + super(elementsCanBeMarked, ElementsChooser.<T>getMarkStateDescriptor()); } public ElementsChooser(List<T> elements, boolean marked) { - this(elements, marked, true); - } - - private ElementsChooser(@Nullable List<T> elements, boolean marked, boolean elementsCanBeMarked) { - super(new BorderLayout()); - - myTableModel = new MyTableModel(elementsCanBeMarked); - myTable = new Table(myTableModel); - myTable.setShowGrid(false); - myTable.setIntercellSpacing(new Dimension(0, 0)); - myTable.setTableHeader(null); - myTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); - myTable.setColumnSelectionAllowed(false); - JScrollPane pane = ScrollPaneFactory.createScrollPane(myTable); - pane.setPreferredSize(new Dimension(100, 155)); - TableColumnModel columnModel = myTable.getColumnModel(); - - if (elementsCanBeMarked) { - TableColumn checkMarkColumn = columnModel.getColumn(myTableModel.CHECK_MARK_COLUM_INDEX); - TableUtil.setupCheckboxColumn(checkMarkColumn); - checkMarkColumn.setCellRenderer(new CheckMarkColumnCellRenderer(myTable.getDefaultRenderer(Boolean.class))); - } - columnModel.getColumn(myTableModel.ELEMENT_COLUMN_INDEX).setCellRenderer(new MyElementColumnCellRenderer()); - - add(pane, BorderLayout.CENTER); - myTable.registerKeyboardAction( - new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final int[] selectedRows = myTable.getSelectedRows(); - boolean currentlyMarked = true; - for (int selectedRow : selectedRows) { - currentlyMarked = myTableModel.isElementMarked(selectedRow); - if (!currentlyMarked) { - break; - } - } - myTableModel.setMarked(selectedRows, !currentlyMarked); - } - }, - KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), - JComponent.WHEN_FOCUSED - ); - - final SpeedSearchBase<JBTable> speedSearch = new SpeedSearchBase<JBTable>(myTable) { - @Override - public int getSelectedIndex() { - return myTable.getSelectedRow(); - } - - @Override - protected int convertIndexToModel(int viewIndex) { - return myTable.convertRowIndexToModel(viewIndex); - } - - @Override - public Object[] getAllElements() { - final int count = myTableModel.getRowCount(); - Object[] elements = new Object[count]; - for (int idx = 0; idx < count; idx++) { - elements[idx] = myTableModel.getElementAt(idx); - } - return elements; - } - - @Override - public String getElementText(Object element) { - return getItemText((T)element); - } - - @Override - public void selectElement(Object element, String selectedText) { - final int count = myTableModel.getRowCount(); - for (int row = 0; row < count; row++) { - if (element.equals(myTableModel.getElementAt(row))) { - final int viewRow = myTable.convertRowIndexToView(row); - myTable.getSelectionModel().setSelectionInterval(viewRow, viewRow); - TableUtil.scrollSelectionToVisible(myTable); - break; - } - } - } - }; - speedSearch.setComparator(new SpeedSearchComparator(false)); - setElements(elements, marked); - installActions(myTable); - } - - private static void installActions(JTable table) { - InputMap inputMap = table.getInputMap(WHEN_FOCUSED); - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), "selectLastRow"); - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), "selectFirstRow"); - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, InputEvent.SHIFT_DOWN_MASK), "selectFirstRowExtendSelection"); - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, InputEvent.SHIFT_DOWN_MASK), "selectLastRowExtendSelection"); - } - - @NotNull - @Override - public StatusText getEmptyText() { - return myTable.getEmptyText(); - } - - @NotNull - @Override - public ExpandableItemsHandler<TableCell> getExpandableItemsHandler() { - return myTable.getExpandableItemsHandler(); - } - - @Override - public void setExpandableItemsEnabled(boolean enabled) { - myTable.setExpandableItemsEnabled(enabled); - } - - public void setSingleSelectionMode() { - myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - } - - public void refresh() { - myTableModel.fireTableDataChanged(); - } - - public void refresh(T element) { - final int row = myTableModel.getElementRow(element); - if (row >= 0) { - myTableModel.fireTableRowsUpdated(row, row); - } - } - - private int[] mySavedSelection = null; - public void saveSelection() { - mySavedSelection = myTable.getSelectedRows(); - } - - public void restoreSelection() { - if (mySavedSelection != null) { - TableUtil.selectRows(myTable, mySavedSelection); - mySavedSelection = null; - } - } - - public boolean isColorUnmarkedElements() { - return myColorUnmarkedElements; - } - - public void setColorUnmarkedElements(boolean colorUnmarkedElements) { - myColorUnmarkedElements = colorUnmarkedElements; + super(elements, marked, ElementsChooser.<T>getMarkStateDescriptor()); } public void addElementsMarkListener(ElementsMarkListener<T> listener) { - myListeners.add(listener); + addElementsMarkListener(new ElementsMarkStateListenerAdapter<T>(listener)); } public void removeElementsMarkListener(ElementsMarkListener<T> listener) { - myListeners.remove(listener); - } - - public void addListSelectionListener(ListSelectionListener listener) { - myTable.getSelectionModel().addListSelectionListener(listener); - } - public void removeListSelectionListener(ListSelectionListener listener) { - myTable.getSelectionModel().removeListSelectionListener(listener); + removeElementsMarkListener(new ElementsMarkStateListenerAdapter<T>(listener)); } public void addElement(T element, final boolean isMarked) { - addElement(element, isMarked, element instanceof ElementProperties ? (ElementProperties)element : null); + addElement(element, getMarkState(isMarked)); } /** * Check if element is marked + * * @param element an element to test * @return true if element is marked */ public boolean isElementMarked(T element) { - final int elementRow = myTableModel.getElementRow(element); - return myTableModel.isElementMarked(elementRow); + return getElementMarkState(element); } /** - * Check if element is marked + * Update element mark + * * @param element an element to test - * @param marked a new value of mark. + * @param marked a new value of mark. */ public void setElementMarked(T element, boolean marked) { - final int elementRow = myTableModel.getElementRow(element); - myTableModel.setMarked(elementRow, marked); - } - - - public void removeElement(T element) { - final int elementRow = myTableModel.getElementRow(element); - if (elementRow < 0) { - return; // no such element - } - final boolean wasSelected = myTable.getSelectionModel().isSelectedIndex(elementRow); - - myTableModel.removeElement(element); - myElementToPropertiesMap.remove(element); - - if (wasSelected) { - final int rowCount = myTableModel.getRowCount(); - if (rowCount > 0) { - selectRow(elementRow % rowCount); - } - else { - myTable.getSelectionModel().clearSelection(); - } - } - myTable.requestFocus(); + setElementMarkState(element, getMarkState(marked)); } - public void removeAllElements() { - myTableModel.removeAllElements(); - myTable.getSelectionModel().clearSelection(); - } - - private void selectRow(final int row) { - myTable.getSelectionModel().setSelectionInterval(row, row); - myTable.scrollRectToVisible(myTable.getCellRect(row, 0, true)); - } - - public void moveElement(T element, int newRow) { - final int elementRow = myTableModel.getElementRow(element); - if (elementRow < 0 || elementRow == newRow || newRow < 0 || newRow >= myTableModel.getRowCount()) { - return; - } - final boolean wasSelected = myTable.getSelectionModel().isSelectedIndex(elementRow); - myTableModel.changeElementRow(element, newRow); - if (wasSelected) { - selectRow(newRow); - } - } - - public interface ElementProperties { - @Nullable - Icon getIcon(); - @Nullable - Color getColor(); - } public void addElement(T element, final boolean isMarked, ElementProperties elementProperties) { - myTableModel.addElement(element, isMarked); - myElementToPropertiesMap.put(element, elementProperties); - selectRow(myTableModel.getRowCount() - 1); - myTable.requestFocus(); - } - - public void setElementProperties(T element, ElementProperties properties) { - myElementToPropertiesMap.put(element, properties); + addElement(element, getMarkState(isMarked), elementProperties); } public void setElements(List<T> elements, boolean marked) { - myTableModel.clear(); - myTableModel.addElements(elements, marked); - } - - @Nullable - public T getSelectedElement() { - final int selectedRow = getSelectedElementRow(); - return selectedRow < 0? null : myTableModel.getElementAt(selectedRow); - } - - public int getSelectedElementRow() { - return myTable.getSelectedRow(); - } - - @NotNull - public List<T> getSelectedElements() { - final List<T> elements = new ArrayList<T>(); - final int[] selectedRows = myTable.getSelectedRows(); - for (int selectedRow : selectedRows) { - if (selectedRow < 0) { - continue; - } - elements.add(myTableModel.getElementAt(selectedRow)); - } - return elements; - } - - public void selectElements(Collection<? extends T> elements) { - if (elements.isEmpty()) { - myTable.clearSelection(); - return; - } - final int[] rows = getElementsRows(elements); - TableUtil.selectRows(myTable, rows); - TableUtil.scrollSelectionToVisible(myTable); - myTable.requestFocus(); - } - - private int[] getElementsRows(final Collection<? extends T> elements) { - final int[] rows = new int[elements.size()]; - int index = 0; - for (final T element : elements) { - rows[index++] = myTable.convertRowIndexToView(myTableModel.getElementRow(element)); - } - return rows; + setElements(elements, getMarkState(marked)); } public void markElements(Collection<T> elements) { - myTableModel.setMarked(getElementsRows(elements), true); + markElements(elements, Boolean.TRUE); } @NotNull public List<T> getMarkedElements() { - final int count = myTableModel.getRowCount(); + Map<T, Boolean> elementMarkStates = getElementMarkStates(); List<T> elements = new ArrayList<T>(); - for (int idx = 0; idx < count; idx++) { - final T element = myTableModel.getElementAt(idx); - if (myTableModel.isElementMarked(idx)) { - elements.add(element); + for (Map.Entry<T, Boolean> entry : elementMarkStates.entrySet()) { + if (entry.getValue()) { + elements.add(entry.getKey()); } } return elements; } - public void sort(Comparator<T> comparator) { - myTableModel.sort(comparator); - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - myTable.setRowSelectionAllowed(enabled); - myTableModel.fireTableDataChanged(); - } - - public void stopEditing() { - TableCellEditor editor = myTable.getCellEditor(); - if (editor != null) { - editor.stopCellEditing(); - } - } - - public JComponent getComponent() { - return myTable; - } - public void invertSelection() { final int count = getElementCount(); for (int i = 0; i < count; i++) { @@ -401,264 +107,89 @@ public class ElementsChooser<T> extends JPanel implements ComponentWithEmptyText } public void setAllElementsMarked(boolean marked) { - final int[] rows = new int[myTableModel.getRowCount()]; - for (int idx = 0; idx < rows.length; idx++) { - rows[idx] = idx; - } - myTableModel.setMarked(rows, marked); - } - - private void notifyElementMarked(T element, boolean isMarked) { - for (ElementsMarkListener<T> listener : myListeners) { - listener.elementMarkChanged(element, isMarked); - } + setAllElementsMarked(getMarkState(marked)); } - public void clear() { - myTableModel.clear(); - myElementToPropertiesMap.clear(); + private static Boolean getMarkState(boolean marked) { + return marked; } - public int getElementCount() { - return myTableModel.getRowCount(); + @SuppressWarnings("unchecked") + private static <T> MarkStateDescriptor<T, Boolean> getMarkStateDescriptor() { + return MARK_STATE_DESCRIPTOR; } - public T getElementAt(int row) { - return myTableModel.getElementAt(row); - } - - public void disableElement(T element) { - myDisabledMap.put(element, Boolean.TRUE); - } - - private final class MyTableModel extends AbstractTableModel { - private final List<T> myElements = new ArrayList<T>(); - private final Map<T, Boolean> myMarkedMap = new HashMap<T, Boolean>(); - public final int CHECK_MARK_COLUM_INDEX; - public final int ELEMENT_COLUMN_INDEX; - private final boolean myElementsCanBeMarked; - - public MyTableModel(final boolean elementsCanBeMarked) { - myElementsCanBeMarked = elementsCanBeMarked; - if (elementsCanBeMarked) { - CHECK_MARK_COLUM_INDEX = 0; - ELEMENT_COLUMN_INDEX = 1; - } - else { - CHECK_MARK_COLUM_INDEX = -1; - ELEMENT_COLUMN_INDEX = 0; - } - } - - public void sort(Comparator<T> comparator) { - Collections.sort(myElements, comparator); - fireTableDataChanged(); - } - - public T getElementAt(int index) { - return myElements.get(index); - } - - public boolean isElementMarked(int index) { - final T element = myElements.get(index); - final Boolean isMarked = myMarkedMap.get(element); - return isMarked.booleanValue(); - } - - private void addElement(T element, boolean isMarked) { - myElements.add(element); - myMarkedMap.put(element, isMarked? Boolean.TRUE : Boolean.FALSE); - int row = myElements.size() - 1; - fireTableRowsInserted(row, row); - } - - private void addElements(@Nullable List<T> elements, boolean isMarked) { - if (elements == null || elements.isEmpty()) { - return; - } - for (final T element : elements) { - myElements.add(element); - myMarkedMap.put(element, isMarked ? Boolean.TRUE : Boolean.FALSE); - } - fireTableRowsInserted(myElements.size() - elements.size(), myElements.size() - 1); - } - - public void removeElement(T element) { - final boolean reallyRemoved = myElements.remove(element); - if (reallyRemoved) { - myMarkedMap.remove(element); - fireTableDataChanged(); - } - } - - public void changeElementRow(T element, int row) { - final boolean reallyRemoved = myElements.remove(element); - if (reallyRemoved) { - myElements.add(row, element); - fireTableDataChanged(); - } - } - - public int getElementRow(T element) { - return myElements.indexOf(element); - } - - public void removeAllElements() { - myElements.clear(); - fireTableDataChanged(); - } - - public void removeRows(int[] rows) { - final List<T> toRemove = new ArrayList<T>(); - for (int row : rows) { - final T element = myElements.get(row); - toRemove.add(element); - myMarkedMap.remove(element); - } - myElements.removeAll(toRemove); - fireTableDataChanged(); - } - + private static class BooleanMarkStateDescriptor<T> implements MarkStateDescriptor<T, Boolean> { + @NotNull @Override - public int getRowCount() { - return myElements.size(); + public Boolean getDefaultState(@NotNull T element) { + return Boolean.FALSE; } + @NotNull @Override - public int getColumnCount() { - return myElementsCanBeMarked? 2 : 1; + public Boolean getNextState(@NotNull T element, @NotNull Boolean state) { + return !state; } - @Override @Nullable - public Object getValueAt(int rowIndex, int columnIndex) { - T element = myElements.get(rowIndex); - if (columnIndex == ELEMENT_COLUMN_INDEX) { - return element; - } - if (columnIndex == CHECK_MARK_COLUM_INDEX) { - return myMarkedMap.get(element); - } - return null; - } - @Override - public void setValueAt(Object aValue, int rowIndex, int columnIndex) { - if (columnIndex == CHECK_MARK_COLUM_INDEX) { - setMarked(rowIndex, ((Boolean)aValue).booleanValue()); - } - } - - private void setMarked(int rowIndex, final boolean marked) { - final T element = myElements.get(rowIndex); - final Boolean newValue = marked? Boolean.TRUE : Boolean.FALSE; - final Boolean prevValue = myMarkedMap.put(element, newValue); - fireTableRowsUpdated(rowIndex, rowIndex); - if (!newValue.equals(prevValue)) { - notifyElementMarked(element, marked); - } - } - - private void setMarked(int[] rows, final boolean marked) { - if (rows == null || rows.length == 0) { - return; - } - int firstRow = Integer.MAX_VALUE; - int lastRow = Integer.MIN_VALUE; - final Boolean newValue = marked? Boolean.TRUE : Boolean.FALSE; - for (final int row : rows) { - final T element = myElements.get(row); - final Boolean prevValue = myMarkedMap.put(element, newValue); - if (!newValue.equals(prevValue)) { - notifyElementMarked(element, newValue.booleanValue()); + public Boolean getNextState(@NotNull Map<T, Boolean> elementsWithStates) { + boolean currentlyMarked = true; + for (Boolean state : elementsWithStates.values()) { + currentlyMarked = state; + if (!currentlyMarked) { + break; } - firstRow = Math.min(firstRow, row); - lastRow = Math.max(lastRow, row); } - fireTableRowsUpdated(firstRow, lastRow); + return !currentlyMarked; } @Override - public Class getColumnClass(int columnIndex) { - if (columnIndex == CHECK_MARK_COLUM_INDEX) { - return Boolean.class; - } - return super.getColumnClass(columnIndex); + public boolean isMarked(@NotNull Boolean state) { + return state; } + @Nullable @Override - public boolean isCellEditable(int rowIndex, int columnIndex) { - if (!isEnabled() || columnIndex != CHECK_MARK_COLUM_INDEX) { - return false; - } - final T o = (T)getValueAt(rowIndex, ELEMENT_COLUMN_INDEX); - return myDisabledMap.get(o) == null; + public Boolean getMarkState(@Nullable Object value) { + return value instanceof Boolean ? ((Boolean)value) : null; } - public void clear() { - myElements.clear(); - myMarkedMap.clear(); - fireTableDataChanged(); + @Nullable + @Override + public TableCellRenderer getMarkRenderer() { + return null; } } - protected String getItemText(@NotNull T value) { - return value.toString(); - } + private static class ElementsMarkStateListenerAdapter<T> implements ElementsMarkStateListener<T, Boolean> { + private final ElementsMarkListener<T> myListener; - @Nullable - protected Icon getItemIcon(@NotNull T value) { - return null; - } + public ElementsMarkStateListenerAdapter(ElementsMarkListener<T> listener) { + myListener = listener; + } - private class MyElementColumnCellRenderer extends DefaultTableCellRenderer { @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - final Color color = UIUtil.getTableFocusCellBackground(); - Component component; - T t = (T)value; - try { - UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, table.getSelectionBackground()); - component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - setText(t != null ? getItemText(t) : ""); - if (component instanceof JLabel) { - ((JLabel)component).setBorder(noFocusBorder); - } - } - finally { - UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, color); - } - final MyTableModel model = (MyTableModel)table.getModel(); - component.setEnabled(ElementsChooser.this.isEnabled() && (!myColorUnmarkedElements || model.isElementMarked(row))); - final ElementProperties properties = myElementToPropertiesMap.get(t); - if (component instanceof JLabel) { - final Icon icon = properties != null ? properties.getIcon() : t != null ? getItemIcon(t) : null; - JLabel label = (JLabel)component; - label.setIcon(icon); - label.setDisabledIcon(icon); - } - component.setForeground(properties != null && properties.getColor() != null ? - properties.getColor() : - isSelected ? table.getSelectionForeground() : table.getForeground()); - return component; + public void elementMarkChanged(T element, Boolean markState) { + myListener.elementMarkChanged(element, markState); } - } - private class CheckMarkColumnCellRenderer implements TableCellRenderer { - private final TableCellRenderer myDelegate; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ElementsMarkStateListenerAdapter that = (ElementsMarkStateListenerAdapter)o; - public CheckMarkColumnCellRenderer(TableCellRenderer delegate) { - myDelegate = delegate; + if (!myListener.equals(that.myListener)) return false; + + return true; } @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Component component = myDelegate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - component.setEnabled(isEnabled()); - if (component instanceof JComponent) { - ((JComponent)component).setBorder(null); - } - return component; + public int hashCode() { + return myListener.hashCode(); } } } diff --git a/platform/platform-impl/src/com/intellij/ide/util/MultiStateElementsChooser.java b/platform/platform-impl/src/com/intellij/ide/util/MultiStateElementsChooser.java new file mode 100644 index 000000000000..bccbd7b144ac --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/util/MultiStateElementsChooser.java @@ -0,0 +1,692 @@ +/* + * Copyright 2000-2014 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.ide.util; + +import com.intellij.ui.*; +import com.intellij.ui.table.JBTable; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.ComponentWithEmptyText; +import com.intellij.util.ui.StatusText; +import com.intellij.util.ui.Table; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.util.*; +import java.util.List; + +public class MultiStateElementsChooser<T, S> extends JPanel implements ComponentWithEmptyText, ComponentWithExpandableItems<TableCell> { + private MarkStateDescriptor<T, S> myMarkStateDescriptor; + private JBTable myTable = null; + private MyTableModel myTableModel = null; + private boolean myColorUnmarkedElements = true; + private final List<ElementsMarkStateListener<T, S>> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); + private final Map<T,ElementProperties> myElementToPropertiesMap = new HashMap<T, ElementProperties>(); + private final Map<T, Boolean> myDisabledMap = new HashMap<T, Boolean>(); + + public interface ElementsMarkStateListener<T, S> { + void elementMarkChanged(T element, S markState); + } + + public interface MarkStateDescriptor<T, S> { + @NotNull + S getDefaultState(@NotNull T element); + + @NotNull + S getNextState(@NotNull T element, @NotNull S state); + + @Nullable + S getNextState(@NotNull Map<T, S> elementsWithStates); + + boolean isMarked(@NotNull S state); + + @Nullable + S getMarkState(@Nullable Object value); + + @Nullable + TableCellRenderer getMarkRenderer(); + } + + public MultiStateElementsChooser(final boolean elementsCanBeMarked, MarkStateDescriptor<T, S> markStateDescriptor) { + this(null, null, elementsCanBeMarked, markStateDescriptor); + } + + public MultiStateElementsChooser(List<T> elements, S markState, MarkStateDescriptor<T, S> markStateDescriptor) { + this(elements, markState, true, markStateDescriptor); + } + + private MultiStateElementsChooser(@Nullable List<T> elements, + S markState, + boolean elementsCanBeMarked, + MarkStateDescriptor<T, S> markStateDescriptor) { + super(new BorderLayout()); + + myMarkStateDescriptor = markStateDescriptor; + + myTableModel = new MyTableModel(elementsCanBeMarked); + myTable = new Table(myTableModel); + myTable.setShowGrid(false); + myTable.setIntercellSpacing(new Dimension(0, 0)); + myTable.setTableHeader(null); + myTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + myTable.setColumnSelectionAllowed(false); + JScrollPane pane = ScrollPaneFactory.createScrollPane(myTable); + pane.setPreferredSize(new Dimension(100, 155)); + TableColumnModel columnModel = myTable.getColumnModel(); + + if (elementsCanBeMarked) { + TableColumn checkMarkColumn = columnModel.getColumn(myTableModel.CHECK_MARK_COLUM_INDEX); + TableUtil.setupCheckboxColumn(checkMarkColumn); + TableCellRenderer checkMarkRenderer = myMarkStateDescriptor.getMarkRenderer(); + if (checkMarkRenderer == null) { + checkMarkRenderer = new CheckMarkColumnCellRenderer(myTable.getDefaultRenderer(Boolean.class)); + } + checkMarkColumn.setCellRenderer(checkMarkRenderer); + } + columnModel.getColumn(myTableModel.ELEMENT_COLUMN_INDEX).setCellRenderer(new MyElementColumnCellRenderer()); + + add(pane, BorderLayout.CENTER); + myTable.registerKeyboardAction( + new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + final int[] selectedRows = myTable.getSelectedRows(); + Map<T, S> selectedElements = new LinkedHashMap<T, S>(selectedRows.length); + for (int selectedRow : selectedRows) { + selectedElements.put(myTableModel.getElementAt(selectedRow), myTableModel.getElementMarkState(selectedRow)); + } + S nextState = myMarkStateDescriptor.getNextState(selectedElements); + if (nextState != null) { + myTableModel.setMarkState(selectedRows, nextState); + } + } + }, + KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), + JComponent.WHEN_FOCUSED + ); + + final SpeedSearchBase<JBTable> speedSearch = new SpeedSearchBase<JBTable>(myTable) { + @Override + public int getSelectedIndex() { + return myTable.getSelectedRow(); + } + + @Override + protected int convertIndexToModel(int viewIndex) { + return myTable.convertRowIndexToModel(viewIndex); + } + + @Override + public Object[] getAllElements() { + final int count = myTableModel.getRowCount(); + Object[] elements = new Object[count]; + for (int idx = 0; idx < count; idx++) { + elements[idx] = myTableModel.getElementAt(idx); + } + return elements; + } + + @Override + public String getElementText(Object element) { + return getItemText((T)element); + } + + @Override + public void selectElement(Object element, String selectedText) { + final int count = myTableModel.getRowCount(); + for (int row = 0; row < count; row++) { + if (element.equals(myTableModel.getElementAt(row))) { + final int viewRow = myTable.convertRowIndexToView(row); + myTable.getSelectionModel().setSelectionInterval(viewRow, viewRow); + TableUtil.scrollSelectionToVisible(myTable); + break; + } + } + } + }; + speedSearch.setComparator(new SpeedSearchComparator(false)); + setElements(elements, markState); + installActions(myTable); + } + + private static void installActions(JTable table) { + InputMap inputMap = table.getInputMap(WHEN_FOCUSED); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), "selectLastRow"); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), "selectFirstRow"); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, InputEvent.SHIFT_DOWN_MASK), "selectFirstRowExtendSelection"); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, InputEvent.SHIFT_DOWN_MASK), "selectLastRowExtendSelection"); + } + + @NotNull + @Override + public StatusText getEmptyText() { + return myTable.getEmptyText(); + } + + @NotNull + @Override + public ExpandableItemsHandler<TableCell> getExpandableItemsHandler() { + return myTable.getExpandableItemsHandler(); + } + + @Override + public void setExpandableItemsEnabled(boolean enabled) { + myTable.setExpandableItemsEnabled(enabled); + } + + public void setSingleSelectionMode() { + myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + + public void refresh() { + myTableModel.fireTableDataChanged(); + } + + public void refresh(T element) { + final int row = myTableModel.getElementRow(element); + if (row >= 0) { + myTableModel.fireTableRowsUpdated(row, row); + } + } + + private int[] mySavedSelection = null; + public void saveSelection() { + mySavedSelection = myTable.getSelectedRows(); + } + + public void restoreSelection() { + if (mySavedSelection != null) { + TableUtil.selectRows(myTable, mySavedSelection); + mySavedSelection = null; + } + } + + public boolean isColorUnmarkedElements() { + return myColorUnmarkedElements; + } + + public void setColorUnmarkedElements(boolean colorUnmarkedElements) { + myColorUnmarkedElements = colorUnmarkedElements; + } + + public void addElementsMarkListener(ElementsMarkStateListener<T, S> listener) { + myListeners.add(listener); + } + + public void removeElementsMarkListener(ElementsMarkStateListener<T, S> listener) { + myListeners.remove(listener); + } + + public void addListSelectionListener(ListSelectionListener listener) { + myTable.getSelectionModel().addListSelectionListener(listener); + } + public void removeListSelectionListener(ListSelectionListener listener) { + myTable.getSelectionModel().removeListSelectionListener(listener); + } + + public void addElement(T element, final S markState) { + addElement(element, markState, element instanceof ElementProperties ? (ElementProperties)element : null); + } + + /** + * Gets element mark state + * @param element an element to test + * @return state of element + */ + public S getElementMarkState(T element) { + final int elementRow = myTableModel.getElementRow(element); + return myTableModel.getElementMarkState(elementRow); + } + + /** + * Update element mark state + * @param element an element to test + * @param markState a new value of mark state + */ + public void setElementMarkState(T element, S markState) { + final int elementRow = myTableModel.getElementRow(element); + myTableModel.setMarkState(elementRow, markState); + } + + + public void removeElement(T element) { + final int elementRow = myTableModel.getElementRow(element); + if (elementRow < 0) { + return; // no such element + } + final boolean wasSelected = myTable.getSelectionModel().isSelectedIndex(elementRow); + + myTableModel.removeElement(element); + myElementToPropertiesMap.remove(element); + + if (wasSelected) { + final int rowCount = myTableModel.getRowCount(); + if (rowCount > 0) { + selectRow(elementRow % rowCount); + } + else { + myTable.getSelectionModel().clearSelection(); + } + } + myTable.requestFocus(); + } + + public void removeAllElements() { + myTableModel.removeAllElements(); + myTable.getSelectionModel().clearSelection(); + } + + private void selectRow(final int row) { + myTable.getSelectionModel().setSelectionInterval(row, row); + myTable.scrollRectToVisible(myTable.getCellRect(row, 0, true)); + } + + public void moveElement(T element, int newRow) { + final int elementRow = myTableModel.getElementRow(element); + if (elementRow < 0 || elementRow == newRow || newRow < 0 || newRow >= myTableModel.getRowCount()) { + return; + } + final boolean wasSelected = myTable.getSelectionModel().isSelectedIndex(elementRow); + myTableModel.changeElementRow(element, newRow); + if (wasSelected) { + selectRow(newRow); + } + } + + public interface ElementProperties { + @Nullable + Icon getIcon(); + @Nullable + Color getColor(); + } + + public void addElement(T element, final S markState, ElementProperties elementProperties) { + myTableModel.addElement(element, markState); + myElementToPropertiesMap.put(element, elementProperties); + selectRow(myTableModel.getRowCount() - 1); + myTable.requestFocus(); + } + + public void setElementProperties(T element, ElementProperties properties) { + myElementToPropertiesMap.put(element, properties); + } + + public void setElements(List<T> elements, S markState) { + myTableModel.clear(); + myTableModel.addElements(elements, markState); + } + + @Nullable + public T getSelectedElement() { + final int selectedRow = getSelectedElementRow(); + return selectedRow < 0? null : myTableModel.getElementAt(selectedRow); + } + + public int getSelectedElementRow() { + return myTable.getSelectedRow(); + } + + @NotNull + public List<T> getSelectedElements() { + final List<T> elements = new ArrayList<T>(); + final int[] selectedRows = myTable.getSelectedRows(); + for (int selectedRow : selectedRows) { + if (selectedRow < 0) { + continue; + } + elements.add(myTableModel.getElementAt(selectedRow)); + } + return elements; + } + + public void selectElements(Collection<? extends T> elements) { + if (elements.isEmpty()) { + myTable.clearSelection(); + return; + } + final int[] rows = getElementsRows(elements); + TableUtil.selectRows(myTable, rows); + TableUtil.scrollSelectionToVisible(myTable); + myTable.requestFocus(); + } + + private int[] getElementsRows(final Collection<? extends T> elements) { + final int[] rows = new int[elements.size()]; + int index = 0; + for (final T element : elements) { + rows[index++] = myTable.convertRowIndexToView(myTableModel.getElementRow(element)); + } + return rows; + } + + public void markElements(Collection<T> elements, S markState) { + myTableModel.setMarkState(getElementsRows(elements), markState); + } + + @NotNull + public Map<T, S> getElementMarkStates() { + final int count = myTableModel.getRowCount(); + Map<T, S> elements = new LinkedHashMap<T, S>(); + for (int idx = 0; idx < count; idx++) { + final T element = myTableModel.getElementAt(idx); + elements.put(element, myTableModel.getElementMarkState(idx)); + } + return elements; + } + + public void sort(Comparator<T> comparator) { + myTableModel.sort(comparator); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + myTable.setRowSelectionAllowed(enabled); + myTableModel.fireTableDataChanged(); + } + + public void stopEditing() { + TableCellEditor editor = myTable.getCellEditor(); + if (editor != null) { + editor.stopCellEditing(); + } + } + + public JComponent getComponent() { + return myTable; + } + + public void setAllElementsMarked(S markState) { + final int[] rows = new int[myTableModel.getRowCount()]; + for (int idx = 0; idx < rows.length; idx++) { + rows[idx] = idx; + } + myTableModel.setMarkState(rows, markState); + } + + private void notifyElementMarked(T element, S markState) { + for (ElementsMarkStateListener<T, S> listener : myListeners) { + listener.elementMarkChanged(element, markState); + } + } + + public void clear() { + myTableModel.clear(); + myElementToPropertiesMap.clear(); + } + + public int getElementCount() { + return myTableModel.getRowCount(); + } + + public T getElementAt(int row) { + return myTableModel.getElementAt(row); + } + + public void disableElement(T element) { + myDisabledMap.put(element, Boolean.TRUE); + } + + private final class MyTableModel extends AbstractTableModel { + private final List<T> myElements = new ArrayList<T>(); + private final Map<T, S> myMarkedMap = new HashMap<T, S>(); + public final int CHECK_MARK_COLUM_INDEX; + public final int ELEMENT_COLUMN_INDEX; + private final boolean myElementsCanBeMarked; + + public MyTableModel(final boolean elementsCanBeMarked) { + myElementsCanBeMarked = elementsCanBeMarked; + if (elementsCanBeMarked) { + CHECK_MARK_COLUM_INDEX = 0; + ELEMENT_COLUMN_INDEX = 1; + } + else { + CHECK_MARK_COLUM_INDEX = -1; + ELEMENT_COLUMN_INDEX = 0; + } + } + + public void sort(Comparator<T> comparator) { + Collections.sort(myElements, comparator); + fireTableDataChanged(); + } + + public T getElementAt(int index) { + return myElements.get(index); + } + + public S getElementMarkState(int index) { + final T element = myElements.get(index); + return myMarkedMap.get(element); + } + + private void addElement(T element, S markState) { + myElements.add(element); + myMarkedMap.put(element, notNullMarkState(element, markState)); + int row = myElements.size() - 1; + fireTableRowsInserted(row, row); + } + + private void addElements(@Nullable List<T> elements, S markState) { + if (elements == null || elements.isEmpty()) { + return; + } + for (final T element : elements) { + myElements.add(element); + myMarkedMap.put(element, notNullMarkState(element, markState)); + } + fireTableRowsInserted(myElements.size() - elements.size(), myElements.size() - 1); + } + + public void removeElement(T element) { + final boolean reallyRemoved = myElements.remove(element); + if (reallyRemoved) { + myMarkedMap.remove(element); + fireTableDataChanged(); + } + } + + public void changeElementRow(T element, int row) { + final boolean reallyRemoved = myElements.remove(element); + if (reallyRemoved) { + myElements.add(row, element); + fireTableDataChanged(); + } + } + + public int getElementRow(T element) { + return myElements.indexOf(element); + } + + public void removeAllElements() { + myElements.clear(); + fireTableDataChanged(); + } + + public void removeRows(int[] rows) { + final List<T> toRemove = new ArrayList<T>(); + for (int row : rows) { + final T element = myElements.get(row); + toRemove.add(element); + myMarkedMap.remove(element); + } + myElements.removeAll(toRemove); + fireTableDataChanged(); + } + + @Override + public int getRowCount() { + return myElements.size(); + } + + @Override + public int getColumnCount() { + return myElementsCanBeMarked? 2 : 1; + } + + @Override + @Nullable + public Object getValueAt(int rowIndex, int columnIndex) { + T element = myElements.get(rowIndex); + if (columnIndex == ELEMENT_COLUMN_INDEX) { + return element; + } + if (columnIndex == CHECK_MARK_COLUM_INDEX) { + return myMarkedMap.get(element); + } + return null; + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + if (columnIndex == CHECK_MARK_COLUM_INDEX) { + S nextState = myMarkStateDescriptor.getMarkState(aValue); + if (nextState == null) { + T element = myTableModel.getElementAt(rowIndex); + S currentState = myTableModel.getElementMarkState(rowIndex); + nextState = myMarkStateDescriptor.getNextState(element, currentState); + } + setMarkState(rowIndex, nextState); + } + } + + private void setMarkState(int rowIndex, final S markState) { + final T element = myElements.get(rowIndex); + final S newValue = notNullMarkState(element, markState); + final S prevValue = myMarkedMap.put(element, newValue); + fireTableRowsUpdated(rowIndex, rowIndex); + if (!newValue.equals(prevValue)) { + notifyElementMarked(element, newValue); + } + } + + private void setMarkState(int[] rows, final S markState) { + if (rows == null || rows.length == 0) { + return; + } + int firstRow = Integer.MAX_VALUE; + int lastRow = Integer.MIN_VALUE; + for (final int row : rows) { + final T element = myElements.get(row); + final S newValue = notNullMarkState(element, markState); + final S prevValue = myMarkedMap.put(element, newValue); + if (!newValue.equals(prevValue)) { + notifyElementMarked(element, newValue); + } + firstRow = Math.min(firstRow, row); + lastRow = Math.max(lastRow, row); + } + fireTableRowsUpdated(firstRow, lastRow); + } + + @NotNull + private S notNullMarkState(T element, S markState) { + return markState != null ? markState : myMarkStateDescriptor.getDefaultState(element); + } + + @Override + public Class getColumnClass(int columnIndex) { + if (columnIndex == CHECK_MARK_COLUM_INDEX) { + return Boolean.class; + } + return super.getColumnClass(columnIndex); + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + if (!isEnabled() || columnIndex != CHECK_MARK_COLUM_INDEX) { + return false; + } + final T o = (T)getValueAt(rowIndex, ELEMENT_COLUMN_INDEX); + return myDisabledMap.get(o) == null; + } + + public void clear() { + myElements.clear(); + myMarkedMap.clear(); + fireTableDataChanged(); + } + } + + protected String getItemText(@NotNull T value) { + return value.toString(); + } + + @Nullable + protected Icon getItemIcon(@NotNull T value) { + return null; + } + + private class MyElementColumnCellRenderer extends DefaultTableCellRenderer { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + final Color color = UIUtil.getTableFocusCellBackground(); + Component component; + T t = (T)value; + try { + UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, table.getSelectionBackground()); + component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setText(t != null ? getItemText(t) : ""); + if (component instanceof JLabel) { + ((JLabel)component).setBorder(noFocusBorder); + } + } + finally { + UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, color); + } + final MyTableModel model = (MyTableModel)table.getModel(); + component.setEnabled(MultiStateElementsChooser.this.isEnabled() && + (!myColorUnmarkedElements || myMarkStateDescriptor.isMarked(model.getElementMarkState(row)))); + final ElementProperties properties = myElementToPropertiesMap.get(t); + if (component instanceof JLabel) { + final Icon icon = properties != null ? properties.getIcon() : t != null ? getItemIcon(t) : null; + JLabel label = (JLabel)component; + label.setIcon(icon); + label.setDisabledIcon(icon); + } + component.setForeground(properties != null && properties.getColor() != null ? + properties.getColor() : + isSelected ? table.getSelectionForeground() : table.getForeground()); + return component; + } + } + + private class CheckMarkColumnCellRenderer implements TableCellRenderer { + private final TableCellRenderer myDelegate; + + public CheckMarkColumnCellRenderer(TableCellRenderer delegate) { + myDelegate = delegate; + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component component = myDelegate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + component.setEnabled(isEnabled()); + if (component instanceof JComponent) { + ((JComponent)component).setBorder(null); + } + return component; + } + } +} |