/*******************************************************************************
* Copyright (c) 2013 RelationWare, Benno Luthiger
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* RelationWare, Benno Luthiger
******************************************************************************/
package org.ripla.rap.util;
import java.util.Arrays;
import java.util.Collections;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.List;
/**
* Multiselect component with two lists: left side for available items and right
* side for selected items. The middle column contains two action buttons to
* move the selected items from the one side to the other<br/>
* The width and height of this widget can be set by the methods
* <code>setListHeight()</code> and <code>setListWidth()</code> methods. The
* component's width is calculated by <code>2*listWidth + 80px</code> (used for
* the middle column).
*
* @author Luthiger
*/
@SuppressWarnings("serial")
public class TwinColSelect extends Composite {
private final List listOrig;
private final List listTarget;
/**
* TwinColSelect constructor.
*
* @param inParent
* {@link Composite}
*/
public TwinColSelect(final Composite inParent) {
super(inParent, SWT.NONE);
final GridLayout lLayout = new GridLayout(3, false);
lLayout.marginWidth = 0;
setLayout(lLayout);
setLayoutData(GridLayoutHelper.createFillLayoutData());
final Composite lCol1 = createColumns(this);
final Composite lCol2 = createColumns(this);
final Composite lCol3 = createColumns(this);
lCol3.setLayoutData(GridLayoutHelper.createFillLayoutData());
listOrig = new List(lCol1, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER);
listOrig.setLayoutData(GridLayoutHelper.createFillLayoutData());
listOrig.addMouseListener(new MouseAdapter() {
@Override
public void mouseDoubleClick(final MouseEvent inEvent) {
final String[] lSelection = ((List) inEvent.widget)
.getSelection();
if (lSelection != null && lSelection.length > 0) {
itemMover(listOrig, listTarget);
}
}
});
final Composite lButtons = new Composite(lCol2, SWT.NONE);
lButtons.setLayout(createRowLayout());
final Button lAdd = new Button(lButtons, SWT.PUSH);
lAdd.setText("»"); // >>
lAdd.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent inEvent) {
itemMover(listOrig, listTarget);
}
});
final Button lRemove = new Button(lButtons, SWT.PUSH);
lRemove.setText("«"); // <<
lRemove.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent inEvent) {
itemMover(listTarget, listOrig);
}
});
listTarget = new List(lCol3, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER);
listTarget.setLayoutData(GridLayoutHelper.createFillLayoutData());
listTarget.addMouseListener(new MouseAdapter() {
@Override
public void mouseDoubleClick(final MouseEvent inEvent) {
final String[] lSelection = ((List) inEvent.widget)
.getSelection();
if (lSelection != null && lSelection.length > 0) {
itemMover(listTarget, listOrig);
}
}
});
}
private void itemMover(final List inFrom, final List inTo) {
inTo.setItems(addSorted(inTo.getItems(), inFrom.getSelection()));
inFrom.remove(inFrom.getSelectionIndices());
}
/**
* Sorting method made protected (instead of private) for testing purposes.
*
* @param inSorted
* String[] a (long) list of items, must be sorted
* @param inToMerge
* String[] a list of items to be merged into the sorted list
* @return String[] the sorted array
*/
protected String[] addSorted(final String[] inSorted,
final String[] inToMerge) {
final String[] out = new String[inSorted.length + inToMerge.length];
int lCursor = 0;
int lUsed = 0;
for (final String lToAdd : inToMerge) {
for (int i = lCursor - lUsed; i < inSorted.length; i++) {
final int lComparison = lToAdd.compareTo(inSorted[i]);
if (lComparison < 0) {
out[lCursor] = lToAdd;
lCursor++;
lUsed++;
break;
} else if (lComparison == 0) {
out[lCursor] = inSorted[i];
lCursor++;
break;
} else if (lComparison > 0) {
out[lCursor] = inSorted[i];
lCursor++;
}
}
}
// process remaining in sorted input
for (int i = lCursor - lUsed; i < inSorted.length; i++) {
out[lCursor] = inSorted[i];
lCursor++;
}
// process remaining in input to add
for (int i = lUsed; i < inToMerge.length; i++) {
out[lCursor] = inToMerge[i];
lCursor++;
}
return out;
}
private Composite createColumns(final Composite inColumns) {
final Composite outColumn = new Composite(inColumns, SWT.NONE);
final GridLayout lLayout = GridLayoutHelper.createGridLayout();
lLayout.marginRight = 15;
outColumn.setLayout(lLayout);
outColumn.setLayoutData(GridLayoutHelper.createFillLayoutData());
return outColumn;
}
private RowLayout createRowLayout() {
final RowLayout outLayout = new RowLayout(SWT.VERTICAL);
outLayout.marginTop = 0;
outLayout.marginLeft = 0;
outLayout.marginRight = 0;
outLayout.marginBottom = 0;
outLayout.spacing = 10;
outLayout.fill = true;
outLayout.wrap = false;
return outLayout;
}
/**
* Sets the receiver's items to be the given array of items.
*
* @param inItems
* String[]
*/
public void setItems(final String[] inItems) {
final java.util.List<String> lItems = Arrays.asList(inItems);
Collections.sort(lItems);
listOrig.setItems(lItems.toArray(new String[] {}));
}
/**
* Sets the height of the control.
*
* @param inHeight
* int height of list in pixels
*/
public void setListHeight(final int inHeight) {
((GridData) listOrig.getLayoutData()).heightHint = inHeight;
((GridData) listTarget.getLayoutData()).heightHint = inHeight;
}
/**
* Sets the width of the list controls. <br/>
* <b>Note</b>: both lists have equal width. The component's width is
* calculated by <code>2*listWidth + 80px</code> (used for the middle
* column).
*
* @param inWidth
* int width of list in pixels
*/
public void setListWidth(final int inWidth) {
((GridData) listOrig.getLayoutData()).widthHint = inWidth;
((GridData) listTarget.getLayoutData()).widthHint = inWidth;
}
/**
* Returns an array of <code>String</code>s that are currently selected in
* the receiver. An empty array indicates that no items are selected.
*
* @return String[] an array representing the selection
*/
public String[] getSelection() {
return listTarget.getSelection();
}
}