1 /*******************************************************************************
2 * Copyright (c) 2000, 2003 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.wizards.dialogfields;
13 import java.util.ArrayList;
14 import java.util.Iterator;
15 import java.util.List;
17 import net.sourceforge.phpdt.internal.ui.util.PixelConverter;
18 import net.sourceforge.phpdt.internal.ui.util.SWTUtil;
21 //import org.eclipse.jface.text.Assert;
22 import org.eclipse.core.runtime.Assert;
23 import org.eclipse.jface.viewers.DoubleClickEvent;
24 import org.eclipse.jface.viewers.IDoubleClickListener;
25 import org.eclipse.jface.viewers.ILabelProvider;
26 import org.eclipse.jface.viewers.ISelection;
27 import org.eclipse.jface.viewers.ISelectionChangedListener;
28 import org.eclipse.jface.viewers.IStructuredSelection;
29 import org.eclipse.jface.viewers.ITreeContentProvider;
30 import org.eclipse.jface.viewers.SelectionChangedEvent;
31 import org.eclipse.jface.viewers.StructuredSelection;
32 import org.eclipse.jface.viewers.TreeViewer;
33 import org.eclipse.jface.viewers.Viewer;
34 import org.eclipse.jface.viewers.ViewerSorter;
35 import org.eclipse.swt.SWT;
36 import org.eclipse.swt.events.KeyAdapter;
37 import org.eclipse.swt.events.KeyEvent;
38 import org.eclipse.swt.events.SelectionEvent;
39 import org.eclipse.swt.events.SelectionListener;
40 import org.eclipse.swt.layout.GridData;
41 import org.eclipse.swt.layout.GridLayout;
42 import org.eclipse.swt.widgets.Button;
43 import org.eclipse.swt.widgets.Composite;
44 import org.eclipse.swt.widgets.Control;
45 import org.eclipse.swt.widgets.Display;
46 import org.eclipse.swt.widgets.Label;
47 import org.eclipse.swt.widgets.Tree;
50 * A list with a button bar. Typical buttons are 'Add', 'Remove', 'Up' and
51 * 'Down'. List model is independend of widget creation. DialogFields controls
52 * are: Label, List and Composite containing buttons.
54 public class TreeListDialogField extends DialogField {
56 protected TreeViewer fTree;
58 protected ILabelProvider fLabelProvider;
60 protected TreeViewerAdapter fTreeViewerAdapter;
62 protected List fElements;
64 protected ViewerSorter fViewerSorter;
66 protected String[] fButtonLabels;
68 private Button[] fButtonControls;
70 private boolean[] fButtonsEnabled;
72 private int fRemoveButtonIndex;
74 private int fUpButtonIndex;
76 private int fDownButtonIndex;
78 private Label fLastSeparator;
80 private Tree fTreeControl;
82 private Composite fButtonsControl;
84 private ISelection fSelectionWhenEnabled;
86 private ITreeListAdapter fTreeAdapter;
88 private Object fParentElement;
90 private int fTreeExpandLevel;
94 * Can be <code>null</code>.
96 public TreeListDialogField(ITreeListAdapter adapter, String[] buttonLabels,
97 ILabelProvider lprovider) {
99 fTreeAdapter = adapter;
101 fLabelProvider = lprovider;
102 fTreeViewerAdapter = new TreeViewerAdapter();
103 fParentElement = this;
105 fElements = new ArrayList(10);
107 fButtonLabels = buttonLabels;
108 if (fButtonLabels != null) {
109 int nButtons = fButtonLabels.length;
110 fButtonsEnabled = new boolean[nButtons];
111 for (int i = 0; i < nButtons; i++) {
112 fButtonsEnabled[i] = true;
118 fButtonsControl = null;
120 fRemoveButtonIndex = -1;
122 fDownButtonIndex = -1;
124 fTreeExpandLevel = 0;
128 * Sets the index of the 'remove' button in the button label array passed in
129 * the constructor. The behaviour of the button marked as the 'remove'
130 * button will then behandled internally. (enable state, button invocation
133 public void setRemoveButtonIndex(int removeButtonIndex) {
134 Assert.isTrue(removeButtonIndex < fButtonLabels.length);
135 fRemoveButtonIndex = removeButtonIndex;
139 * Sets the index of the 'up' button in the button label array passed in the
140 * constructor. The behaviour of the button marked as the 'up' button will
141 * then behandled internally. (enable state, button invocation behaviour)
143 public void setUpButtonIndex(int upButtonIndex) {
144 Assert.isTrue(upButtonIndex < fButtonLabels.length);
145 fUpButtonIndex = upButtonIndex;
149 * Sets the index of the 'down' button in the button label array passed in
150 * the constructor. The behaviour of the button marked as the 'down' button
151 * will then be handled internally. (enable state, button invocation
154 public void setDownButtonIndex(int downButtonIndex) {
155 Assert.isTrue(downButtonIndex < fButtonLabels.length);
156 fDownButtonIndex = downButtonIndex;
160 * Sets the viewerSorter.
162 * @param viewerSorter
163 * The viewerSorter to set
165 public void setViewerSorter(ViewerSorter viewerSorter) {
166 fViewerSorter = viewerSorter;
170 * Sets the viewerSorter.
172 * @param viewerSorter
173 * The viewerSorter to set
175 public void setTreeExpansionLevel(int level) {
176 fTreeExpandLevel = level;
178 fTree.expandToLevel(level);
182 // ------ adapter communication
184 private void buttonPressed(int index) {
185 if (!managedButtonPressed(index) && fTreeAdapter != null) {
186 fTreeAdapter.customButtonPressed(this, index);
191 * Checks if the button pressed is handled internally
193 * @return Returns true if button has been handled.
195 protected boolean managedButtonPressed(int index) {
196 if (index == fRemoveButtonIndex) {
198 } else if (index == fUpButtonIndex) {
200 } else if (index == fDownButtonIndex) {
208 // ------ layout helpers
211 * @see DialogField#doFillIntoGrid
213 public Control[] doFillIntoGrid(Composite parent, int nColumns) {
214 PixelConverter converter = new PixelConverter(parent);
216 assertEnoughColumns(nColumns);
218 Label label = getLabelControl(parent);
219 GridData gd = gridDataForLabel(1);
220 gd.verticalAlignment = GridData.BEGINNING;
221 label.setLayoutData(gd);
223 Control list = getTreeControl(parent);
225 gd.horizontalAlignment = GridData.FILL;
226 gd.grabExcessHorizontalSpace = false;
227 gd.verticalAlignment = GridData.FILL;
228 gd.grabExcessVerticalSpace = true;
229 gd.horizontalSpan = nColumns - 2;
230 gd.widthHint = converter.convertWidthInCharsToPixels(50);
231 gd.heightHint = converter.convertHeightInCharsToPixels(6);
233 list.setLayoutData(gd);
235 Composite buttons = getButtonBox(parent);
237 gd.horizontalAlignment = GridData.FILL;
238 gd.grabExcessHorizontalSpace = false;
239 gd.verticalAlignment = GridData.FILL;
240 gd.grabExcessVerticalSpace = true;
241 gd.horizontalSpan = 1;
242 buttons.setLayoutData(gd);
244 return new Control[] { label, list, buttons };
248 * @see DialogField#getNumberOfControls
250 public int getNumberOfControls() {
255 * Sets the minimal width of the buttons. Must be called after widget
258 public void setButtonsMinWidth(int minWidth) {
259 if (fLastSeparator != null) {
260 ((GridData) fLastSeparator.getLayoutData()).widthHint = minWidth;
264 // ------ ui creation
267 * Returns the tree control. When called the first time, the control will be
271 * parent composite when called the first time, or
272 * <code>null</code> after.
274 public Control getTreeControl(Composite parent) {
275 if (fTreeControl == null) {
276 assertCompositeNotNull(parent);
278 fTree = createTreeViewer(parent);
280 fTreeControl = (Tree) fTree.getControl();
281 fTreeControl.addKeyListener(new KeyAdapter() {
282 public void keyPressed(KeyEvent e) {
286 fTree.setAutoExpandLevel(99);
287 fTree.setContentProvider(fTreeViewerAdapter);
288 fTree.setLabelProvider(fLabelProvider);
289 fTree.addSelectionChangedListener(fTreeViewerAdapter);
290 fTree.addDoubleClickListener(fTreeViewerAdapter);
292 fTree.setInput(fParentElement);
293 fTree.expandToLevel(fTreeExpandLevel);
295 if (fViewerSorter != null) {
296 fTree.setSorter(fViewerSorter);
299 fTreeControl.setEnabled(isEnabled());
300 if (fSelectionWhenEnabled != null) {
301 postSetSelection(fSelectionWhenEnabled);
308 * Returns the internally used table viewer.
310 public TreeViewer getTreeViewer() {
315 * Subclasses may override to specify a different style.
317 protected int getTreeStyle() {
318 int style = SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL;
322 protected TreeViewer createTreeViewer(Composite parent) {
323 Tree tree = new Tree(parent, getTreeStyle());
324 return new TreeViewer(tree);
327 protected Button createButton(Composite parent, String label,
328 SelectionListener listener) {
329 Button button = new Button(parent, SWT.PUSH);
330 button.setText(label);
331 button.addSelectionListener(listener);
332 GridData gd = new GridData();
333 gd.horizontalAlignment = GridData.FILL;
334 gd.grabExcessHorizontalSpace = true;
335 gd.verticalAlignment = GridData.BEGINNING;
336 gd.heightHint = SWTUtil.getButtonHeightHint(button);
337 gd.widthHint = SWTUtil.getButtonWidthHint(button);
339 button.setLayoutData(gd);
343 private Label createSeparator(Composite parent) {
344 Label separator = new Label(parent, SWT.NONE);
345 separator.setVisible(false);
346 GridData gd = new GridData();
347 gd.horizontalAlignment = GridData.FILL;
348 gd.verticalAlignment = GridData.BEGINNING;
350 separator.setLayoutData(gd);
355 * Returns the composite containing the buttons. When called the first time,
356 * the control will be created.
359 * parent composite when called the first time, or
360 * <code>null</code> after.
362 public Composite getButtonBox(Composite parent) {
363 if (fButtonsControl == null) {
364 assertCompositeNotNull(parent);
366 SelectionListener listener = new SelectionListener() {
367 public void widgetDefaultSelected(SelectionEvent e) {
371 public void widgetSelected(SelectionEvent e) {
376 Composite contents = new Composite(parent, SWT.NULL);
377 GridLayout layout = new GridLayout();
378 layout.marginWidth = 0;
379 layout.marginHeight = 0;
380 contents.setLayout(layout);
382 if (fButtonLabels != null) {
383 fButtonControls = new Button[fButtonLabels.length];
384 for (int i = 0; i < fButtonLabels.length; i++) {
385 String currLabel = fButtonLabels[i];
386 if (currLabel != null) {
387 fButtonControls[i] = createButton(contents, currLabel,
389 fButtonControls[i].setEnabled(isEnabled()
390 && fButtonsEnabled[i]);
392 fButtonControls[i] = null;
393 createSeparator(contents);
398 fLastSeparator = createSeparator(contents);
401 fButtonsControl = contents;
404 return fButtonsControl;
407 private void doButtonSelected(SelectionEvent e) {
408 if (fButtonControls != null) {
409 for (int i = 0; i < fButtonControls.length; i++) {
410 if (e.widget == fButtonControls[i]) {
419 * Handles key events in the table viewer. Specifically when the delete key
422 protected void handleKeyPressed(KeyEvent event) {
423 if (event.character == SWT.DEL && event.stateMask == 0) {
424 if (fRemoveButtonIndex != -1
425 && isButtonEnabled(fTree.getSelection(), fRemoveButtonIndex)) {
426 managedButtonPressed(fRemoveButtonIndex);
430 fTreeAdapter.keyPressed(this, event);
433 // ------ enable / disable management
436 * @see DialogField#dialogFieldChanged
438 public void dialogFieldChanged() {
439 super.dialogFieldChanged();
444 * Updates the enable state of the all buttons
446 protected void updateButtonState() {
447 if (fButtonControls != null) {
448 ISelection sel = fTree.getSelection();
449 for (int i = 0; i < fButtonControls.length; i++) {
450 Button button = fButtonControls[i];
451 if (isOkToUse(button)) {
452 button.setEnabled(isButtonEnabled(sel, i));
458 protected boolean containsAttributes(List selected) {
459 for (int i = 0; i < selected.size(); i++) {
460 if (!fElements.contains(selected.get(i))) {
467 protected boolean getManagedButtonState(ISelection sel, int index) {
468 List selected = getSelectedElements();
469 boolean hasAttributes = containsAttributes(selected);
470 if (index == fRemoveButtonIndex) {
471 return !selected.isEmpty() && !hasAttributes;
472 } else if (index == fUpButtonIndex) {
473 return !sel.isEmpty() && !hasAttributes && canMoveUp(selected);
474 } else if (index == fDownButtonIndex) {
475 return !sel.isEmpty() && !hasAttributes && canMoveDown(selected);
481 * @see DialogField#updateEnableState
483 protected void updateEnableState() {
484 super.updateEnableState();
486 boolean enabled = isEnabled();
487 if (isOkToUse(fTreeControl)) {
489 fSelectionWhenEnabled = fTree.getSelection();
490 selectElements(null);
492 selectElements(fSelectionWhenEnabled);
493 fSelectionWhenEnabled = null;
495 fTreeControl.setEnabled(enabled);
501 * Sets a button enabled or disabled.
503 public void enableButton(int index, boolean enable) {
504 if (fButtonsEnabled != null && index < fButtonsEnabled.length) {
505 fButtonsEnabled[index] = enable;
510 private boolean isButtonEnabled(ISelection sel, int index) {
511 boolean extraState = getManagedButtonState(sel, index);
512 return isEnabled() && extraState && fButtonsEnabled[index];
515 // ------ model access
518 * Sets the elements shown in the list.
520 public void setElements(List elements) {
521 fElements = new ArrayList(elements);
524 fTree.expandToLevel(fTreeExpandLevel);
526 dialogFieldChanged();
530 * Gets the elements shown in the list. The list returned is a copy, so it
531 * can be modified by the user.
533 public List getElements() {
534 return new ArrayList(fElements);
538 * Gets the element shown at the given index.
540 public Object getElement(int index) {
541 return fElements.get(index);
545 * Gets the index of an element in the list or -1 if element is not in list.
547 public int getIndexOfElement(Object elem) {
548 return fElements.indexOf(elem);
552 * Replace an element.
554 public void replaceElement(Object oldElement, Object newElement)
555 throws IllegalArgumentException {
556 int idx = fElements.indexOf(oldElement);
558 fElements.set(idx, newElement);
560 List selected = getSelectedElements();
561 if (selected.remove(oldElement)) {
562 selected.add(newElement);
564 boolean isExpanded = fTree.getExpandedState(oldElement);
565 fTree.remove(oldElement);
566 fTree.add(fParentElement, newElement);
568 fTree.expandToLevel(newElement, fTreeExpandLevel);
570 selectElements(new StructuredSelection(selected));
572 dialogFieldChanged();
574 throw new IllegalArgumentException();
579 * Adds an element at the end of the tree list.
581 public void addElement(Object element) {
582 if (fElements.contains(element)) {
585 fElements.add(element);
587 fTree.add(fParentElement, element);
588 fTree.expandToLevel(element, fTreeExpandLevel);
590 dialogFieldChanged();
594 * Adds elements at the end of the tree list.
596 public void addElements(List elements) {
597 int nElements = elements.size();
601 ArrayList elementsToAdd = new ArrayList(nElements);
603 for (int i = 0; i < nElements; i++) {
604 Object elem = elements.get(i);
605 if (!fElements.contains(elem)) {
606 elementsToAdd.add(elem);
609 fElements.addAll(elementsToAdd);
611 fTree.add(fParentElement, elementsToAdd.toArray());
612 for (int i = 0; i < elementsToAdd.size(); i++) {
613 fTree.expandToLevel(elementsToAdd.get(i), fTreeExpandLevel);
616 dialogFieldChanged();
621 * Adds an element at a position.
623 public void insertElementAt(Object element, int index) {
624 if (fElements.contains(element)) {
627 fElements.add(index, element);
629 fTree.add(fParentElement, element);
630 if (fTreeExpandLevel != -1) {
631 fTree.expandToLevel(element, fTreeExpandLevel);
635 dialogFieldChanged();
639 * Adds an element at a position.
641 public void removeAllElements() {
642 if (fElements.size() > 0) {
645 dialogFieldChanged();
650 * Removes an element from the list.
652 public void removeElement(Object element) throws IllegalArgumentException {
653 if (fElements.remove(element)) {
655 fTree.remove(element);
657 dialogFieldChanged();
659 throw new IllegalArgumentException();
664 * Removes elements from the list.
666 public void removeElements(List elements) {
667 if (elements.size() > 0) {
668 fElements.removeAll(elements);
670 fTree.remove(elements.toArray());
672 dialogFieldChanged();
677 * Gets the number of elements
679 public int getSize() {
680 return fElements.size();
683 public void selectElements(ISelection selection) {
684 fSelectionWhenEnabled = selection;
686 fTree.setSelection(selection, true);
690 public void selectFirstElement() {
691 Object element = null;
692 if (fViewerSorter != null) {
693 Object[] arr = fElements.toArray();
694 fViewerSorter.sort(fTree, arr);
695 if (arr.length > 0) {
699 if (fElements.size() > 0) {
700 element = fElements.get(0);
703 if (element != null) {
704 selectElements(new StructuredSelection(element));
708 public void postSetSelection(final ISelection selection) {
709 if (isOkToUse(fTreeControl)) {
710 Display d = fTreeControl.getDisplay();
711 d.asyncExec(new Runnable() {
713 if (isOkToUse(fTreeControl)) {
714 selectElements(selection);
722 * Refreshes the tree.
724 public void refresh() {
731 * Refreshes the tree.
733 public void refresh(Object element) {
735 fTree.refresh(element);
739 // ------- list maintenance
741 private List moveUp(List elements, List move) {
742 int nElements = elements.size();
743 List res = new ArrayList(nElements);
744 Object floating = null;
745 for (int i = 0; i < nElements; i++) {
746 Object curr = elements.get(i);
747 if (move.contains(curr)) {
750 if (floating != null) {
756 if (floating != null) {
762 private void moveUp(List toMoveUp) {
763 if (toMoveUp.size() > 0) {
764 setElements(moveUp(fElements, toMoveUp));
765 fTree.reveal(toMoveUp.get(0));
769 private void moveDown(List toMoveDown) {
770 if (toMoveDown.size() > 0) {
771 setElements(reverse(moveUp(reverse(fElements), toMoveDown)));
772 fTree.reveal(toMoveDown.get(toMoveDown.size() - 1));
776 private List reverse(List p) {
777 List reverse = new ArrayList(p.size());
778 for (int i = p.size() - 1; i >= 0; i--) {
779 reverse.add(p.get(i));
784 private void remove() {
785 removeElements(getSelectedElements());
789 moveUp(getSelectedElements());
792 private void down() {
793 moveDown(getSelectedElements());
796 private boolean canMoveUp(List selectedElements) {
797 if (isOkToUse(fTreeControl)) {
798 int nSelected = selectedElements.size();
799 int nElements = fElements.size();
800 for (int i = 0; i < nElements && nSelected > 0; i++) {
801 if (!selectedElements.contains(fElements.get(i))) {
810 private boolean canMoveDown(List selectedElements) {
811 if (isOkToUse(fTreeControl)) {
812 int nSelected = selectedElements.size();
813 for (int i = fElements.size() - 1; i >= 0 && nSelected > 0; i--) {
814 if (!selectedElements.contains(fElements.get(i))) {
824 * Returns the selected elements.
826 public List getSelectedElements() {
827 ArrayList result = new ArrayList();
829 ISelection selection = fTree.getSelection();
830 if (selection instanceof IStructuredSelection) {
831 Iterator iter = ((IStructuredSelection) selection).iterator();
832 while (iter.hasNext()) {
833 result.add(iter.next());
840 public void expandElement(Object element, int level) {
842 fTree.expandToLevel(element, level);
846 // ------- TreeViewerAdapter
848 private class TreeViewerAdapter implements ITreeContentProvider,
849 ISelectionChangedListener, IDoubleClickListener {
851 private final Object[] NO_ELEMENTS = new Object[0];
853 // ------- ITreeContentProvider Interface ------------
855 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
859 public boolean isDeleted(Object element) {
863 public void dispose() {
866 public Object[] getElements(Object obj) {
867 return fElements.toArray();
870 public Object[] getChildren(Object element) {
871 if (fTreeAdapter != null) {
872 return fTreeAdapter.getChildren(TreeListDialogField.this,
878 public Object getParent(Object element) {
879 if (!fElements.contains(element) && fTreeAdapter != null) {
881 .getParent(TreeListDialogField.this, element);
883 return fParentElement;
886 public boolean hasChildren(Object element) {
887 if (fTreeAdapter != null) {
888 return fTreeAdapter.hasChildren(TreeListDialogField.this,
894 // ------- ISelectionChangedListener Interface ------------
896 public void selectionChanged(SelectionChangedEvent event) {
897 doListSelected(event);
903 * @see org.eclipse.jface.viewers.IDoubleClickListener#doubleClick(org.eclipse.jface.viewers.DoubleClickEvent)
905 public void doubleClick(DoubleClickEvent event) {
906 doDoubleClick(event);
911 protected void doListSelected(SelectionChangedEvent event) {
913 if (fTreeAdapter != null) {
914 fTreeAdapter.selectionChanged(this);
918 protected void doDoubleClick(DoubleClickEvent event) {
919 if (fTreeAdapter != null) {
920 fTreeAdapter.doubleClicked(this);