/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.framework.uitools;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.basic.BasicArrowButton;
import org.eclipse.persistence.tools.workbench.framework.context.ApplicationContext;
import org.eclipse.persistence.tools.workbench.framework.ui.view.AbstractPanel;
import org.eclipse.persistence.tools.workbench.uitools.app.ListValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.swing.ListModelAdapter;
import org.eclipse.persistence.tools.workbench.uitools.app.swing.ObjectListSelectionModel;
import org.eclipse.persistence.tools.workbench.uitools.cell.DisplayableListCellRenderer;
/**
* This panel can be used to manage a pair of lists,
* one which contains the "available" items and one which
* contains the "selected" items. The panel displays the
* lists and provides the actions to move the items back
* and forth. Client must provide an adapter that performs
* the actually moving of the items, typically between
* models.
*/
public final class DualListSelectorPanel
extends AbstractPanel
{
ListValueModel availableLVM;
private ListModel availableLM;
private ObjectListSelectionModel availableLSM;
ListValueModel selectedLVM;
private ListModel selectedLM;
private ObjectListSelectionModel selectedLSM;
private Adapter adapter;
private ListCellRenderer listCellRenderer;
private String availableListBoxLabelKey;
private String selectedListBoxLabelKey;
private String selectButtonToolTipKey;
private String deselectButtonToolTipKey;
private Action selectAction;
private Action deselectAction;
// ********** constructor/initialization **********
DualListSelectorPanel(Builder builder) {
super(builder.getContext());
this.availableLVM = builder.getAvailableLVM();
if (this.availableLVM == null) {
throw new NullPointerException();
}
this.availableLM = new ListModelAdapter(this.availableLVM);
this.availableLSM = new ObjectListSelectionModel(this.availableLM);
this.selectedLVM = builder.getSelectedLVM();
if (this.selectedLVM == null) {
throw new NullPointerException();
}
this.selectedLM = new ListModelAdapter(this.selectedLVM);
this.selectedLSM = new ObjectListSelectionModel(this.selectedLM);
this.adapter = builder.getAdapter();
if (this.adapter == null) {
this.adapter = new DefaultAdapter();
}
this.listCellRenderer = builder.getListCellRenderer();
if (this.listCellRenderer == null) {
this.listCellRenderer = new DisplayableListCellRenderer();
}
this.availableListBoxLabelKey = builder.getAvailableListBoxLabelKey();
if (this.availableListBoxLabelKey == null) {
this.availableListBoxLabelKey = "AVAILABLE_ITEMS_LIST_BOX_LABEL";
}
this.selectedListBoxLabelKey = builder.getSelectedListBoxLabelKey();
if (this.selectedListBoxLabelKey == null) {
this.selectedListBoxLabelKey = "SELECTED_ITEMS_LIST_LABEL";
}
this.selectButtonToolTipKey = builder.getSelectButtonToolTipKey();
if (this.selectButtonToolTipKey == null) {
this.selectButtonToolTipKey = "ADD_SELECTED_ITEMS_BUTTON.toolTipText";
}
this.deselectButtonToolTipKey = builder.getDeselectButtonToolTipKey();
if (this.deselectButtonToolTipKey == null) {
this.deselectButtonToolTipKey = "REMOVE_SELECTED_ITEMS_BUTTON.toolTipText";
}
this.addHelpTopicId(this, builder.getHelpTopicID());
this.selectAction = this.buildSelectAction();
this.deselectAction = this.buildDeselectAction();
this.initializeLayout();
}
private Action buildSelectAction() {
Action action = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
DualListSelectorPanel.this.selectItems();
}
};
action.setEnabled(false);
return action;
}
private Action buildDeselectAction() {
Action action = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
DualListSelectorPanel.this.deselectItems();
}
};
action.setEnabled(false);
return action;
}
private void initializeLayout() {
GridBagConstraints constraints = new GridBagConstraints();
// available items
JLabel availableLabel = new JLabel(this.resourceRepository().getString(this.availableListBoxLabelKey));
constraints.gridx = 0;
constraints.gridy = 0;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 0;
constraints.weighty = 0;
constraints.fill = GridBagConstraints.NONE;
constraints.anchor = GridBagConstraints.LINE_START;
constraints.insets = new Insets(0, 0, 0, 0);
this.add(availableLabel, constraints);
Component availableListBox = this.buildListBox(this.availableLM, this.availableLSM, this.selectAction);
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 1;
constraints.gridheight = 2;
constraints.weightx = 0.5;
constraints.weighty = 1;
constraints.fill = GridBagConstraints.BOTH;
constraints.anchor = GridBagConstraints.CENTER;
constraints.insets = new Insets(1, 0, 0, 0);
this.add(availableListBox, constraints);
availableLabel.setLabelFor(availableListBox);
// selected items
JLabel selectedLabel = new JLabel(this.resourceRepository().getString(this.selectedListBoxLabelKey));
constraints.gridx = 2;
constraints.gridy = 0;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 0;
constraints.weighty = 0;
constraints.fill = GridBagConstraints.NONE;
constraints.anchor = GridBagConstraints.LINE_START;
constraints.insets = new Insets(0, 5, 0, 0);
this.add(selectedLabel, constraints);
Component selectedListBox = this.buildListBox(this.selectedLM, this.selectedLSM, this.deselectAction);
constraints.gridx = 2;
constraints.gridy = 1;
constraints.gridwidth = 1;
constraints.gridheight = 2;
constraints.weightx = 0.5;
constraints.weighty = 1;
constraints.fill = GridBagConstraints.BOTH;
constraints.anchor = GridBagConstraints.CENTER;
constraints.insets = new Insets(1, 5, 0, 0);
this.add(selectedListBox, constraints);
selectedLabel.setLabelFor(selectedListBox);
// right arrow button (select)
JButton rightArrowButton = new JButton();
rightArrowButton.setAction(this.selectAction);
rightArrowButton.setIcon(resourceRepository().getIcon("shuttle.right"));
rightArrowButton.setToolTipText(this.resourceRepository().getString(this.selectButtonToolTipKey));
constraints.gridx = 1;
constraints.gridy = 1;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 0;
constraints.weighty = 0;
constraints.fill = GridBagConstraints.NONE;
constraints.anchor = GridBagConstraints.CENTER;
constraints.insets = new Insets(1, 5, 0, 0);
this.add(rightArrowButton, constraints);
// left arrow button (deselect)
JButton leftArrowButton = new JButton();
leftArrowButton.setAction(this.deselectAction);
leftArrowButton.setIcon(resourceRepository().getIcon("shuttle.left"));
leftArrowButton.setToolTipText(this.resourceRepository().getString(this.deselectButtonToolTipKey));
constraints.gridx = 1;
constraints.gridy = 2;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 0;
constraints.weighty = 1;
constraints.fill = GridBagConstraints.NONE;
constraints.anchor = GridBagConstraints.PAGE_START;
constraints.insets = new Insets(5, 5, 5, 0);
this.add(leftArrowButton, constraints);
}
/**
* build a list box that displays the specified list and performs
* the specified action whenever it is double-clicked
*/
private Component buildListBox(ListModel lm, ListSelectionModel lsm, Action action) {
JList listBox = SwingComponentFactory.buildList(lm);
listBox.setSelectionModel(lsm);
listBox.setCellRenderer(this.listCellRenderer);
listBox.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
listBox.addListSelectionListener(new LocalListSelectionListener(action));
listBox.addMouseListener(new DoubleClickActionMouseAdapter(action));
JScrollPane scrollPane = new JScrollPane(listBox);
scrollPane.setPreferredSize(new Dimension(200, 100));
scrollPane.setMinimumSize(new Dimension(200, 100));
return scrollPane;
}
// ********** behavior **********
/**
* delegate to the adapter to "select" the items,
* then select them once they are in the "selected" list
*/
void selectItems() {
Object[] items = this.availableLSM.getSelectedValues();
for (int i = 0; i < items.length; i++) {
this.adapter.select(items[i]);
}
this.selectedLSM.setSelectedValues(items);
}
/**
* delegate to the adapter to "deselect" the items,
* then select them once they are in the "available" list
*/
void deselectItems() {
Object[] items = this.selectedLSM.getSelectedValues();
for (int i = 0; i < items.length; i++) {
this.adapter.deselect(items[i]);
}
this.availableLSM.setSelectedValues(items);
}
// ********** nested classes **********
/**
* an adapter is used by the dual list selector panel
* to move items back and forth between the "available"
* and "selected" lists
*/
public interface Adapter {
/**
* the specified item is to be moved from the "available"
* list to the "selected" list
*/
void select(Object item);
/**
* the specified item is to be moved from the "selected"
* list to the "available" list
*/
void deselect(Object item);
}
/**
* The default adapter assumes that the supplied list value
* models can be modified (which usually is NOT the case).
*/
private class DefaultAdapter implements Adapter {
public void select(Object item) {
this.remove(DualListSelectorPanel.this.availableLVM, item);
this.add(DualListSelectorPanel.this.selectedLVM, item);
}
public void deselect(Object item) {
this.remove(DualListSelectorPanel.this.selectedLVM, item);
this.add(DualListSelectorPanel.this.availableLVM, item);
}
private void remove(ListValueModel lvm, Object item) {
int size = lvm.size();
for (int i = 0; i < size; i++) {
if (lvm.getItem(i) == item) {
lvm.removeItem(i);
return;
}
}
}
private void add(ListValueModel lvm, Object item) {
lvm.addItem(lvm.size(), item);
}
}
/**
* Use this builder to configure and construct a dual
* list selector panel. See the comments on the setters.
*/
public static class Builder {
private ListValueModel availableLVM;
private ListValueModel selectedLVM;
private Adapter adapter;
private ListCellRenderer listCellRenderer;
private ApplicationContext context;
private String availableListBoxLabelKey;
private String selectedListBoxLabelKey;
private String selectButtonToolTipKey;
private String deselectButtonToolTipKey;
private String helpTopicID;
/**
* build a dual list selector panel with the current
* builder configuration
*/
public DualListSelectorPanel buildPanel() {
return new DualListSelectorPanel(this);
}
/**
* set the items that will be displayed in the
* "available" list (the left hand list); REQUIRED
*/
public void setAvailableLVM(ListValueModel availableLVM) {
this.availableLVM = availableLVM;
}
public ListValueModel getAvailableLVM() {
return this.availableLVM;
}
/**
* set the items that will be displayed in the
* "selected" list (the right hand list); REQUIRED
*/
public void setSelectedLVM(ListValueModel selectedLVM) {
this.selectedLVM = selectedLVM;
}
public ListValueModel getSelectedLVM() {
return this.selectedLVM;
}
/**
* set the adapter that will be invoked when items
* are "selected" or "deselected"; the default adapter
* assumes the list value models can be modified directly,
* which is usually NOT the case
*/
public void setAdapter(Adapter adapter) {
this.adapter = adapter;
}
public Adapter getAdapter() {
return this.adapter;
}
/**
* set the renderer used in both lists; the default renderer
* assumes the items in the list implement Displayable
*/
public void setListCellRenderer(ListCellRenderer listCellRenderer) {
this.listCellRenderer = listCellRenderer;
}
public ListCellRenderer getListCellRenderer() {
return this.listCellRenderer;
}
/**
* set the application context that will supply the
* various resources; REQUIRED
*/
public void setContext(ApplicationContext context) {
this.context = context;
}
public ApplicationContext getContext() {
return this.context;
}
/**
* set the key used to look up a label for the
* "available" list box; optional
*/
public void setAvailableListBoxLabelKey(String availableListBoxLabelKey) {
this.availableListBoxLabelKey = availableListBoxLabelKey;
}
public String getAvailableListBoxLabelKey() {
return this.availableListBoxLabelKey;
}
/**
* set the key used to look up a label for the
* "selected" list box; optional
*/
public void setSelectedListBoxLabelKey(String selectedListBoxLabelKey) {
this.selectedListBoxLabelKey = selectedListBoxLabelKey;
}
public String getSelectedListBoxLabelKey() {
return this.selectedListBoxLabelKey;
}
/**
* set the key used to look up a "tool tip" for the
* "select" button; optional
*/
public void setSelectButtonToolTipKey(String selectButtonToolTipKey) {
this.selectButtonToolTipKey = selectButtonToolTipKey;
}
public String getSelectButtonToolTipKey() {
return this.selectButtonToolTipKey;
}
/**
* set the key used to look up a "tool tip" for the
* "deselect" button; optional
*/
public void setDeselectButtonToolTipKey(String deselectButtonToolTipKey) {
this.deselectButtonToolTipKey = deselectButtonToolTipKey;
}
public String getDeselectButtonToolTipKey() {
return this.deselectButtonToolTipKey;
}
/**
* set the Help topic ID used for the entire panel; REQUIRED
*/
public void setHelpTopicID(String helpTopicID) {
this.helpTopicID = helpTopicID;
}
public String getHelpTopicID() {
return this.helpTopicID;
}
}
/**
* A MouseListener that performs an action whenever a double-click
* occurs. The action must ignore the ActionEvent, because we are
* going to pass in a null.
*/
private class DoubleClickActionMouseAdapter extends MouseAdapter {
private Action action;
DoubleClickActionMouseAdapter(Action action) {
super();
this.action = action;
}
public void mouseClicked(MouseEvent event) {
if (event.getClickCount() == 2) {
this.action.actionPerformed(null);
}
}
}
/**
* A ListSelectionListener that enables an action only when
* items in the list box are selected (i.e. the action is disabled
* whenever none of the items in the list box are selected).
*/
private class LocalListSelectionListener implements ListSelectionListener {
private Action action;
LocalListSelectionListener(Action action) {
super();
this.action = action;
}
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
this.action.setEnabled(((JList) e.getSource()).getSelectedIndex() != -1);
}
}
}