/******************************************************************************* * Copyright (c) 2000, 2012 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.gyrex.admin.ui.internal.wizards.dialogfields; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.gyrex.admin.ui.internal.helper.SwtUtil; import org.eclipse.gyrex.admin.ui.internal.widgets.FilteredTree; import org.eclipse.gyrex.admin.ui.internal.widgets.PatternFilter; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.layout.PixelConverter; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Tree; /** * A list with a button bar. Typical buttons are 'Add', 'Remove', 'Up' and * 'Down'. List model is independent of widget creation. DialogFields controls * are: Label, List and Composite containing buttons. */ public class TreeListDialogField extends DialogField { private class TreeViewerAdapter implements ITreeContentProvider, ISelectionChangedListener, IDoubleClickListener { /** serialVersionUID */ private static final long serialVersionUID = 1L; private final Object[] NO_ELEMENTS = new Object[0]; @Override public void dispose() { } @Override public void doubleClick(final DoubleClickEvent event) { doDoubleClick(event); } @Override public Object[] getChildren(final Object element) { if (fTreeAdapter != null) return fTreeAdapter.getChildren(TreeListDialogField.this, element); return NO_ELEMENTS; } @Override public Object[] getElements(final Object obj) { return fElements.toArray(); } @Override public Object getParent(final Object element) { if (!fElements.contains(element) && (fTreeAdapter != null)) return fTreeAdapter.getParent(TreeListDialogField.this, element); return fParentElement; } @Override public boolean hasChildren(final Object element) { if (fTreeAdapter != null) return fTreeAdapter.hasChildren(TreeListDialogField.this, element); return false; } @Override public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) { // will never happen } @Override public void selectionChanged(final SelectionChangedEvent event) { doListSelected(event); } } protected TreeViewer fTree; protected ILabelProvider fLabelProvider; protected TreeViewerAdapter fTreeViewerAdapter; protected List<Object> fElements; protected ViewerComparator fViewerComparator; protected String[] fButtonLabels; private Button[] fButtonControls; private boolean[] fButtonsEnabled; private int fRemoveButtonIndex; private int fUpButtonIndex; private int fDownButtonIndex; private Label fLastSeparator; private Control fTreeControl; private Composite fButtonsControl; private ISelection fSelectionWhenEnabled; private final ITreeListAdapter fTreeAdapter; private final Object fParentElement; private final PatternFilter fPatternFilter; private int fTreeExpandLevel; /** * Creates a new instance. * * @param adapter * Can be <code>null</code>. */ public TreeListDialogField(final ITreeListAdapter adapter, final String[] buttonLabels, final ILabelProvider lprovider) { this(adapter, buttonLabels, lprovider, null); } /** * Creates a new instance backed by a {@link FilteredTree}. * * @param adapter * Can be <code>null</code>. * @param buttonLabels * @param lprovider * @param patternFilter */ public TreeListDialogField(final ITreeListAdapter adapter, final String[] buttonLabels, final ILabelProvider lprovider, final PatternFilter patternFilter) { super(); fTreeAdapter = adapter; fLabelProvider = lprovider; fPatternFilter = patternFilter; fTreeViewerAdapter = new TreeViewerAdapter(); fParentElement = this; fElements = new ArrayList<Object>(10); fButtonLabels = buttonLabels; if (fButtonLabels != null) { final int nButtons = fButtonLabels.length; fButtonsEnabled = new boolean[nButtons]; for (int i = 0; i < nButtons; i++) { fButtonsEnabled[i] = true; } } fTree = null; fTreeControl = null; fButtonsControl = null; fRemoveButtonIndex = -1; fUpButtonIndex = -1; fDownButtonIndex = -1; fTreeExpandLevel = 0; } /** * Adds an element at the end of the tree list. */ public boolean addElement(final Object element) { if (fElements.contains(element)) return false; fElements.add(element); if (isOkToUse(fTreeControl)) { fTree.add(fParentElement, element); fTree.expandToLevel(element, fTreeExpandLevel); } dialogFieldChanged(); return true; } /** * Adds elements at the end of the tree list. */ public boolean addElements(final List<?> elements) { final int nElements = elements.size(); if (nElements > 0) { // filter duplicated final ArrayList<Object> elementsToAdd = new ArrayList<Object>(nElements); for (int i = 0; i < nElements; i++) { final Object elem = elements.get(i); if (!fElements.contains(elem)) { elementsToAdd.add(elem); } } if (!elementsToAdd.isEmpty()) { fElements.addAll(elementsToAdd); if (isOkToUse(fTreeControl)) { fTree.add(fParentElement, elementsToAdd.toArray()); for (int i = 0; i < elementsToAdd.size(); i++) { fTree.expandToLevel(elementsToAdd.get(i), fTreeExpandLevel); } } dialogFieldChanged(); return true; } } return false; } private void buttonPressed(final int index) { if (!managedButtonPressed(index) && (fTreeAdapter != null)) { fTreeAdapter.customButtonPressed(this, index); } } private boolean canMoveDown(final List<Object> selectedElements) { if (isOkToUse(fTreeControl)) { int nSelected = selectedElements.size(); for (int i = fElements.size() - 1; (i >= 0) && (nSelected > 0); i--) { if (!selectedElements.contains(fElements.get(i))) return true; nSelected--; } } return false; } private boolean canMoveUp(final List<Object> selectedElements) { if (isOkToUse(fTreeControl)) { int nSelected = selectedElements.size(); final int nElements = fElements.size(); for (int i = 0; (i < nElements) && (nSelected > 0); i++) { if (!selectedElements.contains(fElements.get(i))) return true; nSelected--; } } return false; } protected boolean containsAttributes(final List<Object> selected) { for (int i = 0; i < selected.size(); i++) { if (!fElements.contains(selected.get(i))) return true; } return false; } protected Button createButton(final Composite parent, final String label, final SelectionListener listener) { final Button button = new Button(parent, SWT.PUSH); button.setFont(parent.getFont()); button.setText(label); button.addSelectionListener(listener); final GridData gd = new GridData(); gd.horizontalAlignment = GridData.FILL; gd.grabExcessHorizontalSpace = true; gd.verticalAlignment = GridData.BEGINNING; gd.widthHint = SwtUtil.getButtonWidthHint(button); button.setLayoutData(gd); return button; } private Label createSeparator(final Composite parent) { final Label separator = new Label(parent, SWT.NONE); separator.setFont(parent.getFont()); separator.setVisible(false); final GridData gd = new GridData(); gd.horizontalAlignment = GridData.FILL; gd.verticalAlignment = GridData.BEGINNING; gd.heightHint = 4; separator.setLayoutData(gd); return separator; } protected TreeViewer createTreeViewer(final Composite parent) { final Tree tree = new Tree(parent, getTreeStyle()); tree.setFont(parent.getFont()); return new TreeViewer(tree); } @Override public void dialogFieldChanged() { super.dialogFieldChanged(); updateButtonState(); } private void doButtonSelected(final SelectionEvent e) { if (fButtonControls != null) { for (int i = 0; i < fButtonControls.length; i++) { if (e.widget == fButtonControls[i]) { buttonPressed(i); return; } } } } protected void doDoubleClick(final DoubleClickEvent event) { if (fTreeAdapter != null) { fTreeAdapter.doubleClicked(this); } } @Override public Control[] doFillIntoGrid(final Composite parent, final int nColumns) { final PixelConverter converter = new PixelConverter(parent); assertEnoughColumns(nColumns); final Label label = getLabelControl(parent); GridData gd = gridDataForLabel(1); gd.verticalAlignment = GridData.BEGINNING; label.setLayoutData(gd); final Control list = getTreeControl(parent); gd = new GridData(); gd.horizontalAlignment = GridData.FILL; gd.grabExcessHorizontalSpace = false; gd.verticalAlignment = GridData.FILL; gd.grabExcessVerticalSpace = true; gd.horizontalSpan = nColumns - 2; gd.widthHint = converter.convertWidthInCharsToPixels(50); gd.heightHint = converter.convertHeightInCharsToPixels(6); list.setLayoutData(gd); final Composite buttons = getButtonBox(parent); gd = new GridData(); gd.horizontalAlignment = GridData.FILL; gd.grabExcessHorizontalSpace = false; gd.verticalAlignment = GridData.FILL; gd.grabExcessVerticalSpace = true; gd.horizontalSpan = 1; buttons.setLayoutData(gd); return new Control[] { label, list, buttons }; } protected void doListSelected(final SelectionChangedEvent event) { updateButtonState(); if (fTreeAdapter != null) { fTreeAdapter.selectionChanged(this); } } private void down() { moveDown(getSelectedElements()); } /** * Sets a button enabled or disabled. */ public void enableButton(final int index, final boolean enable) { if ((fButtonsEnabled != null) && (index < fButtonsEnabled.length)) { fButtonsEnabled[index] = enable; updateButtonState(); } } public void expandElement(final Object element, final int level) { if (isOkToUse(fTreeControl)) { fTree.expandToLevel(element, level); } } /** * Returns the composite containing the buttons. When called the first time, * the control will be created. * * @param parent * The parent composite when called the first time, or * <code>null</code> after. */ public Composite getButtonBox(final Composite parent) { if (fButtonsControl == null) { assertCompositeNotNull(parent); final SelectionListener listener = new SelectionListener() { /** serialVersionUID */ private static final long serialVersionUID = 1L; @Override public void widgetDefaultSelected(final SelectionEvent e) { doButtonSelected(e); } @Override public void widgetSelected(final SelectionEvent e) { doButtonSelected(e); } }; final Composite contents = new Composite(parent, SWT.NONE); contents.setFont(parent.getFont()); final GridLayout layout = new GridLayout(); layout.marginWidth = 0; layout.marginHeight = 0; contents.setLayout(layout); if (fButtonLabels != null) { fButtonControls = new Button[fButtonLabels.length]; for (int i = 0; i < fButtonLabels.length; i++) { final String currLabel = fButtonLabels[i]; if (currLabel != null) { fButtonControls[i] = createButton(contents, currLabel, listener); fButtonControls[i].setEnabled(isEnabled() && fButtonsEnabled[i]); } else { fButtonControls[i] = null; createSeparator(contents); } } } fLastSeparator = createSeparator(contents); updateButtonState(); fButtonsControl = contents; } return fButtonsControl; } /** * Gets the element shown at the given index. */ public Object getElement(final int index) { return fElements.get(index); } /** * Gets the elements shown in the list. The list returned is a copy, so it * can be modified by the user. */ public List<Object> getElements() { return new ArrayList<Object>(fElements); } /** * Gets the index of an element in the list or -1 if element is not in list. */ public int getIndexOfElement(final Object elem) { return fElements.indexOf(elem); } protected boolean getManagedButtonState(final ISelection sel, final int index) { final List<Object> selected = getSelectedElements(); final boolean hasAttributes = containsAttributes(selected); if (index == fRemoveButtonIndex) return !selected.isEmpty() && !hasAttributes; else if (index == fUpButtonIndex) return !sel.isEmpty() && !hasAttributes && canMoveUp(selected); else if (index == fDownButtonIndex) return !sel.isEmpty() && !hasAttributes && canMoveDown(selected); return true; } @Override public int getNumberOfControls() { return 3; } /** * Returns the selected elements. */ public List<Object> getSelectedElements() { final ArrayList<Object> result = new ArrayList<Object>(); if (isOkToUse(fTreeControl)) { final ISelection selection = fTree.getSelection(); if (selection instanceof IStructuredSelection) { final Iterator<?> iter = ((IStructuredSelection) selection).iterator(); while (iter.hasNext()) { result.add(iter.next()); } } } return result; } /** * Gets the number of elements */ public int getSize() { return fElements.size(); } /** * Returns the tree control. When called the first time, the control will be * created. * * @param parent * The parent composite when called the first time, or * <code>null</code> after. */ public Control getTreeControl(final Composite parent) { if (fTreeControl == null) { assertCompositeNotNull(parent); if (fPatternFilter != null) { final FilteredTree filteredTree = new FilteredTree(parent, getTreeStyle(), fPatternFilter, true); filteredTree.setFont(parent.getFont()); fTree = filteredTree.getViewer(); fTreeControl = filteredTree; } else { fTree = createTreeViewer(parent); fTreeControl = fTree.getControl(); } fTree.getTree().addKeyListener(new KeyAdapter() { /** serialVersionUID */ private static final long serialVersionUID = 1L; @Override public void keyPressed(final KeyEvent e) { handleKeyPressed(e); } }); fTree.setContentProvider(fTreeViewerAdapter); fTree.setLabelProvider(fLabelProvider); fTree.addSelectionChangedListener(fTreeViewerAdapter); fTree.addDoubleClickListener(fTreeViewerAdapter); fTree.setInput(fParentElement); fTree.expandToLevel(fTreeExpandLevel); if (fViewerComparator != null) { fTree.setComparator(fViewerComparator); } fTreeControl.setEnabled(isEnabled()); if (fSelectionWhenEnabled != null) { postSetSelection(fSelectionWhenEnabled); } } return fTreeControl; } /** * Subclasses may override to specify a different style. */ protected int getTreeStyle() { final int style = SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL; return style; } /** * Returns the internally used table viewer. */ public TreeViewer getTreeViewer() { return fTree; } /** * Handles key events in the table viewer. Specifically when the delete key * is pressed. */ protected void handleKeyPressed(final KeyEvent event) { if ((event.character == SWT.DEL) && (event.stateMask == 0)) { if ((fRemoveButtonIndex != -1) && isButtonEnabled(fTree.getSelection(), fRemoveButtonIndex)) { managedButtonPressed(fRemoveButtonIndex); return; } } fTreeAdapter.keyPressed(this, event); } /** * Adds an element at a position. */ public void insertElementAt(final Object element, final int index) { if (fElements.contains(element)) return; fElements.add(index, element); if (isOkToUse(fTreeControl)) { fTree.add(fParentElement, element); if (fTreeExpandLevel != -1) { fTree.expandToLevel(element, fTreeExpandLevel); } } dialogFieldChanged(); } private boolean isButtonEnabled(final ISelection sel, final int index) { final boolean extraState = getManagedButtonState(sel, index); return isEnabled() && extraState && fButtonsEnabled[index]; } /** * Checks if the button pressed is handled internally * * @return Returns true if button has been handled. */ protected boolean managedButtonPressed(final int index) { if (index == fRemoveButtonIndex) { remove(); } else if (index == fUpButtonIndex) { up(); } else if (index == fDownButtonIndex) { down(); } else return false; return true; } private void moveDown(final List<Object> toMoveDown) { if (toMoveDown.size() > 0) { setElements(reverse(moveUp(reverse(fElements), toMoveDown))); fTree.reveal(toMoveDown.get(toMoveDown.size() - 1)); } } private void moveUp(final List<Object> toMoveUp) { if (toMoveUp.size() > 0) { setElements(moveUp(fElements, toMoveUp)); fTree.reveal(toMoveUp.get(0)); } } private List<Object> moveUp(final List<Object> elements, final List<Object> move) { final int nElements = elements.size(); final List<Object> res = new ArrayList<Object>(nElements); Object floating = null; for (int i = 0; i < nElements; i++) { final Object curr = elements.get(i); if (move.contains(curr)) { res.add(curr); } else { if (floating != null) { res.add(floating); } floating = curr; } } if (floating != null) { res.add(floating); } return res; } public void postSetSelection(final ISelection selection) { if (isOkToUse(fTreeControl)) { final Display d = fTreeControl.getDisplay(); d.asyncExec(new Runnable() { @Override public void run() { if (isOkToUse(fTreeControl)) { selectElements(selection); } } }); } } /** * Refreshes the tree. */ @Override public void refresh() { super.refresh(); if (isOkToUse(fTreeControl)) { fTree.refresh(); } } /** * Refreshes the tree. */ public void refresh(final Object element) { if (isOkToUse(fTreeControl)) { fTree.refresh(element); } } private void remove() { removeElements(getSelectedElements()); } /** * Adds an element at a position. */ public void removeAllElements() { if (fElements.size() > 0) { fElements.clear(); refresh(); dialogFieldChanged(); } } /** * Removes an element from the list. */ public void removeElement(final Object element) throws IllegalArgumentException { if (fElements.remove(element)) { if (isOkToUse(fTreeControl)) { fTree.remove(element); } dialogFieldChanged(); } else throw new IllegalArgumentException(); } /** * Removes elements from the list. */ public void removeElements(final List<Object> elements) { if (elements.size() > 0) { fElements.removeAll(elements); if (isOkToUse(fTreeControl)) { fTree.remove(elements.toArray()); } dialogFieldChanged(); } } /** * Replace an element. */ public void replaceElement(final Object oldElement, final Object newElement) throws IllegalArgumentException { final int idx = fElements.indexOf(oldElement); if (idx != -1) { fElements.set(idx, newElement); if (isOkToUse(fTreeControl)) { final List<Object> selected = getSelectedElements(); if (selected.remove(oldElement)) { selected.add(newElement); } final boolean isExpanded = fTree.getExpandedState(oldElement); fTree.remove(oldElement); fTree.add(fParentElement, newElement); if (isExpanded) { fTree.expandToLevel(newElement, fTreeExpandLevel); } selectElements(new StructuredSelection(selected)); } dialogFieldChanged(); } else throw new IllegalArgumentException(); } private List<Object> reverse(final List<Object> p) { final List<Object> reverse = new ArrayList<Object>(p.size()); for (int i = p.size() - 1; i >= 0; i--) { reverse.add(p.get(i)); } return reverse; } public void selectElements(final ISelection selection) { fSelectionWhenEnabled = selection; if (isOkToUse(fTreeControl)) { fTree.setSelection(selection, true); } } public void selectFirstElement() { Object element = null; if (fViewerComparator != null) { final Object[] arr = fElements.toArray(); fViewerComparator.sort(fTree, arr); if (arr.length > 0) { element = arr[0]; } } else { if (fElements.size() > 0) { element = fElements.get(0); } } if (element != null) { selectElements(new StructuredSelection(element)); } } /** * Sets the minimal width of the buttons. Must be called after widget * creation. */ public void setButtonsMinWidth(final int minWidth) { if (fLastSeparator != null) { ((GridData) fLastSeparator.getLayoutData()).widthHint = minWidth; } } /** * Sets the index of the 'down' button in the button label array passed in * the constructor. The behavior of the button marked as the 'down' button * will then be handled internally. (enable state, button invocation * behavior) */ public void setDownButtonIndex(final int downButtonIndex) { Assert.isTrue(downButtonIndex < fButtonLabels.length); fDownButtonIndex = downButtonIndex; } /** * Sets the elements shown in the list. */ public void setElements(final List<? extends Object> elements) { fElements = new ArrayList<Object>(elements); refresh(); if (isOkToUse(fTreeControl)) { fTree.expandToLevel(fTreeExpandLevel); } dialogFieldChanged(); } /** * Sets the index of the 'remove' button in the button label array passed in * the constructor. The behavior of the button marked as the 'remove' button * will then be handled internally. (enable state, button invocation * behavior) */ public void setRemoveButtonIndex(final int removeButtonIndex) { Assert.isTrue(removeButtonIndex < fButtonLabels.length); fRemoveButtonIndex = removeButtonIndex; } public void setTreeExpansionLevel(final int level) { fTreeExpandLevel = level; if (isOkToUse(fTreeControl) && (fTreeExpandLevel > 0)) { fTree.expandToLevel(level); } } /** * Sets the index of the 'up' button in the button label array passed in the * constructor. The behavior of the button marked as the 'up' button will * then be handled internally. (enable state, button invocation behavior) */ public void setUpButtonIndex(final int upButtonIndex) { Assert.isTrue(upButtonIndex < fButtonLabels.length); fUpButtonIndex = upButtonIndex; } /** * Sets the viewer comparator. * * @param viewerComparator * The viewer comparator to set */ public void setViewerComparator(final ViewerComparator viewerComparator) { fViewerComparator = viewerComparator; } private void up() { moveUp(getSelectedElements()); } /** * Updates the element. */ public void update(final Object element) { if (isOkToUse(fTreeControl)) { fTree.update(element, null); } } /** * Updates the enable state of the all buttons */ protected void updateButtonState() { if ((fButtonControls != null) && isOkToUse(fTreeControl) && fTreeControl.isEnabled()) { final ISelection sel = fTree.getSelection(); for (int i = 0; i < fButtonControls.length; i++) { final Button button = fButtonControls[i]; if (isOkToUse(button)) { button.setEnabled(isButtonEnabled(sel, i)); } } } } @Override protected void updateEnableState() { super.updateEnableState(); final boolean enabled = isEnabled(); if (isOkToUse(fTreeControl)) { if (!enabled) { if (fSelectionWhenEnabled == null) { fSelectionWhenEnabled = fTree.getSelection(); selectElements(null); } } else if (fSelectionWhenEnabled != null) { selectElements(fSelectionWhenEnabled); } fTreeControl.setEnabled(enabled); } updateButtonState(); } }