blob: cbb35b257ca9f075bcf6e9966940e3bf195bdb42 [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.ui.components;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.impl.ActionButton;
import com.intellij.openapi.actionSystem.impl.PresentationFactory;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.ListUtil;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.util.ui.GridBag;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.Map;
/**
* A UI control which consists of two lists with ability to move elements between them.
* <p/>
* It looks as <a href="http://openfaces.org/documentation/developersGuide/twolistselection.html">here</a>.
*
* @author Konstantin Bulenkov
*/
public class JBMovePanel extends JBPanel {
public static final String MOVE_PANEL_PLACE = "MOVE_PANEL";
public static final InsertPositionStrategy ANCHORING_SELECTION = new InsertPositionStrategy() {
@Override
public int getInsertionIndex(@NotNull Object data, @NotNull JList list) {
int index = list.getSelectedIndex();
DefaultListModel model = (DefaultListModel)list.getModel();
return index < 0 ? model.getSize() : index + 1;
}
};
public static final InsertPositionStrategy NATURAL_ORDER = new InsertPositionStrategy() {
@SuppressWarnings("unchecked")
@Override
public int getInsertionIndex(@NotNull Object data, @NotNull JList list) {
Enumeration elements = ((DefaultListModel)list.getModel()).elements();
int index = 0;
while (elements.hasMoreElements()) {
Object e = elements.nextElement();
// DefaultListModel is type-aware only since java7, so, use raw types until we're on java6.
if (((Comparable)e).compareTo(data) >= 0) {
break;
}
index++;
}
return index;
}
};
@NotNull private final Map<ButtonType, ActionButton> myButtons = new EnumMap<ButtonType, ActionButton>(ButtonType.class);
@NotNull private final ListPanel myLeftPanel = new ListPanel();
@NotNull private final ListPanel myRightPanel = new ListPanel();
@NotNull protected final JList myLeftList;
@NotNull protected final JList myRightList;
@NotNull protected final ActionButton myLeftButton;
@NotNull protected final ActionButton myAllLeftButton;
@NotNull protected final ActionButton myRightButton;
@NotNull protected final ActionButton myAllRightButton;
@NotNull protected final ActionButton myUpButton;
@NotNull protected final ActionButton myDownButton;
@NotNull private InsertPositionStrategy myLeftInsertionStrategy = ANCHORING_SELECTION;
@NotNull private InsertPositionStrategy myRightInsertionStrategy = ANCHORING_SELECTION;
private boolean myActivePreferredSizeProcessing;
public enum ButtonType {LEFT, RIGHT, ALL_LEFT, ALL_RIGHT}
public JBMovePanel(@NotNull JList left, @NotNull JList right) {
super(new GridBagLayout());
assertModelIsEditable(left);
assertModelIsEditable(right);
myLeftList = left;
myRightList = right;
final JPanel leftRightButtonsPanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.MIDDLE));
leftRightButtonsPanel.add(myRightButton = createButton(ButtonType.RIGHT));
leftRightButtonsPanel.add(myAllRightButton = createButton(ButtonType.ALL_RIGHT));
leftRightButtonsPanel.add(myLeftButton = createButton(ButtonType.LEFT));
leftRightButtonsPanel.add(myAllLeftButton = createButton(ButtonType.ALL_LEFT));
myUpButton = createButton(new UpAction());
myDownButton = createButton(new DownAction());
final JPanel upDownButtonsPanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.MIDDLE));
upDownButtonsPanel.add(myUpButton);
upDownButtonsPanel.add(myDownButton);
MouseListener mouseListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() != 2 || e.getButton() != MouseEvent.BUTTON1) {
return;
}
if (e.getSource() == myLeftList) {
doRight();
}
else if (e.getSource() == myRightList) {
doLeft();
}
}
};
myLeftList.addMouseListener(mouseListener);
myRightList.addMouseListener(mouseListener);
GridBag listConstraints = new GridBag().weightx(1).weighty(1).fillCell();
GridBag buttonConstraints = new GridBag().anchor(GridBagConstraints.CENTER);
myLeftPanel.add(ScrollPaneFactory.createScrollPane(left), listConstraints);
add(myLeftPanel, listConstraints);
add(leftRightButtonsPanel, buttonConstraints);
myRightPanel.add(ScrollPaneFactory.createScrollPane(right), listConstraints);
add(myRightPanel, listConstraints);
add(upDownButtonsPanel, buttonConstraints);
}
private static void assertModelIsEditable(@NotNull JList list) {
assert list.getModel() instanceof DefaultListModel : String
.format("List model should extends %s interface", DefaultListModel.class.getName());
}
public void setShowButtons(@NotNull ButtonType... types) {
for (ActionButton button : myButtons.values()) {
button.setVisible(false);
}
for (ButtonType type : types) {
myButtons.get(type).setVisible(true);
}
}
public void setListLabels(@NotNull String left, @NotNull String right) {
// Border insets are used as a component insets (see JComponent.getInsets()). That's why an ugly bottom inset is used when
// we create a border with default insets. That is the reason why we explicitly specify bottom inset as zero.
Insets insets = new Insets(IdeBorderFactory.TITLED_BORDER_TOP_INSET,
IdeBorderFactory.TITLED_BORDER_LEFT_INSET,
0,
IdeBorderFactory.TITLED_BORDER_RIGHT_INSET);
myLeftPanel.setBorder(IdeBorderFactory.createTitledBorder(left, false, insets));
myRightPanel.setBorder(IdeBorderFactory.createTitledBorder(right, false, insets));
}
public void setLeftInsertionStrategy(@NotNull InsertPositionStrategy leftInsertionStrategy) {
myLeftInsertionStrategy = leftInsertionStrategy;
}
// Commented to preserve green code policy until this method is not used. Uncomment when necessary.
//public void setRightInsertionStrategy(@NotNull InsertPositionStrategy rightInsertionStrategy) {
// myRightInsertionStrategy = rightInsertionStrategy;
//}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
myLeftList.setEnabled(enabled);
myRightList.setEnabled(enabled);
for (ActionButton button : myButtons.values()) {
button.setEnabled(enabled);
}
}
@NotNull
private ActionButton createButton(@NotNull final ButtonType type) {
final AnAction action;
switch (type) {
case LEFT:
action = new LeftAction();
break;
case RIGHT:
action = new RightAction();
break;
case ALL_LEFT:
action = new AllLeftAction();
break;
case ALL_RIGHT:
action = new AllRightAction();
break;
default: throw new IllegalArgumentException("Unsupported button type: " + type);
}
ActionButton button = createButton(action);
myButtons.put(type, button);
return button;
}
@NotNull
private static ActionButton createButton(@NotNull final AnAction action) {
PresentationFactory presentationFactory = new PresentationFactory();
Icon icon = AllIcons.Actions.AllLeft;
Dimension size = new Dimension(icon.getIconWidth(), icon.getIconHeight());
return new ActionButton(action, presentationFactory.getPresentation(action), MOVE_PANEL_PLACE, size);
}
protected void doRight() {
moveBetween(myRightList, myRightInsertionStrategy, myLeftList);
}
protected void doLeft() {
moveBetween(myLeftList, myLeftInsertionStrategy, myRightList);
}
protected void doAllLeft() {
moveAllBetween(myLeftList, myRightList);
}
protected void doAllRight() {
moveAllBetween(myRightList, myLeftList);
}
private static void moveBetween(@NotNull JList to, @NotNull InsertPositionStrategy strategy, @NotNull JList from) {
final int[] indices = from.getSelectedIndices();
if (indices.length <= 0) {
return;
}
final Object[] values = from.getSelectedValues();
for (int i = indices.length - 1; i >= 0; i--) {
((DefaultListModel)from.getModel()).remove(indices[i]);
}
if (from.getModel().getSize() > 0) {
int newSelectionIndex = indices[0];
newSelectionIndex = Math.min(from.getModel().getSize() - 1, newSelectionIndex);
from.setSelectedIndex(newSelectionIndex);
}
to.clearSelection();
DefaultListModel toModel = (DefaultListModel)to.getModel();
int newSelectionIndex = -1;
for (Object value : values) {
if (!toModel.contains(value)) {
int i = strategy.getInsertionIndex(value, to);
if (newSelectionIndex < 0) {
newSelectionIndex = i;
}
toModel.add(i, value);
to.addSelectionInterval(i, i);
}
}
}
private static void moveAllBetween(@NotNull JList to, @NotNull JList from) {
final DefaultListModel fromModel = (DefaultListModel)from.getModel();
final DefaultListModel toModel = (DefaultListModel)to.getModel();
while (fromModel.getSize() > 0) {
Object element = fromModel.remove(0);
if (!toModel.contains(element)) {
toModel.addElement(element);
}
}
}
public static void main(String[] args) {
final JBMovePanel panel = new JBMovePanel(new JBList("asdas", "weqrwe", "ads12312", "aZSD23"),
new JBList("123412", "as2341", "aaaaaaaaaaa", "ZZZZZZZZZZ", "12"));
final JFrame test = new JFrame("Test");
test.setContentPane(panel);
test.setSize(500, 500);
test.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
test.setVisible(true);
}
public interface InsertPositionStrategy {
int getInsertionIndex(@NotNull Object data, @NotNull JList list);
}
/**
* The general idea is to layout target lists to use the same width. This wrapper panel controls that.
*/
private class ListPanel extends JPanel {
ListPanel() {
super(new GridBagLayout());
}
@Override
public Dimension getPreferredSize() {
Dimension d1 = super.getPreferredSize();
if (myActivePreferredSizeProcessing) {
return d1;
}
myActivePreferredSizeProcessing = true;
try {
final Dimension d2;
if (myLeftPanel == this) {
d2 = myRightPanel.getPreferredSize();
}
else {
d2 = myLeftPanel.getPreferredSize();
}
return new Dimension(Math.max(d1.width, d2.width), Math.max(d1.height, d2.height));
}
finally {
myActivePreferredSizeProcessing = false;
}
}
}
private class LeftAction extends AnAction {
LeftAction() {
getTemplatePresentation().setIcon(AllIcons.Actions.Left);
}
@Override
public void actionPerformed(AnActionEvent e) {
doLeft();
}
}
private class RightAction extends AnAction {
RightAction() {
getTemplatePresentation().setIcon(AllIcons.Actions.Right);
}
@Override
public void actionPerformed(AnActionEvent e) {
doRight();
}
}
private class AllLeftAction extends AnAction {
AllLeftAction() {
getTemplatePresentation().setIcon(AllIcons.Actions.AllLeft);
}
@Override
public void actionPerformed(AnActionEvent e) {
doAllLeft();
}
}
private class AllRightAction extends AnAction {
AllRightAction() {
getTemplatePresentation().setIcon(AllIcons.Actions.AllRight);
}
@Override
public void actionPerformed(AnActionEvent e) {
doAllRight();
}
}
private class UpAction extends AnAction {
UpAction() {
getTemplatePresentation().setIcon(AllIcons.Actions.UP);
}
@Override
public void actionPerformed(AnActionEvent e) {
ListUtil.moveSelectedItemsUp(myRightList);
}
}
private class DownAction extends AnAction {
DownAction() {
getTemplatePresentation().setIcon(AllIcons.Actions.Down);
}
@Override
public void actionPerformed(AnActionEvent e) {
ListUtil.moveSelectedItemsDown(myRightList);
}
}
}