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;
20 import org.eclipse.jface.util.Assert;
21 import org.eclipse.jface.viewers.DoubleClickEvent;
22 import org.eclipse.jface.viewers.IDoubleClickListener;
23 import org.eclipse.jface.viewers.ILabelProvider;
24 import org.eclipse.jface.viewers.ISelection;
25 import org.eclipse.jface.viewers.ISelectionChangedListener;
26 import org.eclipse.jface.viewers.IStructuredSelection;
27 import org.eclipse.jface.viewers.ITreeContentProvider;
28 import org.eclipse.jface.viewers.SelectionChangedEvent;
29 import org.eclipse.jface.viewers.StructuredSelection;
30 import org.eclipse.jface.viewers.TreeViewer;
31 import org.eclipse.jface.viewers.Viewer;
32 import org.eclipse.jface.viewers.ViewerSorter;
33 import org.eclipse.swt.SWT;
34 import org.eclipse.swt.events.KeyAdapter;
35 import org.eclipse.swt.events.KeyEvent;
36 import org.eclipse.swt.events.SelectionEvent;
37 import org.eclipse.swt.events.SelectionListener;
38 import org.eclipse.swt.layout.GridData;
39 import org.eclipse.swt.layout.GridLayout;
40 import org.eclipse.swt.widgets.Button;
41 import org.eclipse.swt.widgets.Composite;
42 import org.eclipse.swt.widgets.Control;
43 import org.eclipse.swt.widgets.Display;
44 import org.eclipse.swt.widgets.Label;
45 import org.eclipse.swt.widgets.Tree;
48 * A list with a button bar. Typical buttons are 'Add', 'Remove', 'Up' and
49 * 'Down'. List model is independend of widget creation. DialogFields controls
50 * are: Label, List and Composite containing buttons.
52 public class TreeListDialogField extends DialogField {
54 protected TreeViewer fTree;
56 protected ILabelProvider fLabelProvider;
58 protected TreeViewerAdapter fTreeViewerAdapter;
60 protected List fElements;
62 protected ViewerSorter fViewerSorter;
64 protected String[] fButtonLabels;
66 private Button[] fButtonControls;
68 private boolean[] fButtonsEnabled;
70 private int fRemoveButtonIndex;
72 private int fUpButtonIndex;
74 private int fDownButtonIndex;
76 private Label fLastSeparator;
78 private Tree fTreeControl;
80 private Composite fButtonsControl;
82 private ISelection fSelectionWhenEnabled;
84 private ITreeListAdapter fTreeAdapter;
86 private Object fParentElement;
88 private int fTreeExpandLevel;
92 * Can be <code>null</code>.
94 public TreeListDialogField(ITreeListAdapter adapter, String[] buttonLabels,
95 ILabelProvider lprovider) {
97 fTreeAdapter = adapter;
99 fLabelProvider = lprovider;
100 fTreeViewerAdapter = new TreeViewerAdapter();
101 fParentElement = this;
103 fElements = new ArrayList(10);
105 fButtonLabels = buttonLabels;
106 if (fButtonLabels != null) {
107 int nButtons = fButtonLabels.length;
108 fButtonsEnabled = new boolean[nButtons];
109 for (int i = 0; i < nButtons; i++) {
110 fButtonsEnabled[i] = true;
116 fButtonsControl = null;
118 fRemoveButtonIndex = -1;
120 fDownButtonIndex = -1;
122 fTreeExpandLevel = 0;
126 * Sets the index of the 'remove' button in the button label array passed in
127 * the constructor. The behaviour of the button marked as the 'remove'
128 * button will then behandled internally. (enable state, button invocation
131 public void setRemoveButtonIndex(int removeButtonIndex) {
132 Assert.isTrue(removeButtonIndex < fButtonLabels.length);
133 fRemoveButtonIndex = removeButtonIndex;
137 * Sets the index of the 'up' button in the button label array passed in the
138 * constructor. The behaviour of the button marked as the 'up' button will
139 * then behandled internally. (enable state, button invocation behaviour)
141 public void setUpButtonIndex(int upButtonIndex) {
142 Assert.isTrue(upButtonIndex < fButtonLabels.length);
143 fUpButtonIndex = upButtonIndex;
147 * Sets the index of the 'down' button in the button label array passed in
148 * the constructor. The behaviour of the button marked as the 'down' button
149 * will then be handled internally. (enable state, button invocation
152 public void setDownButtonIndex(int downButtonIndex) {
153 Assert.isTrue(downButtonIndex < fButtonLabels.length);
154 fDownButtonIndex = downButtonIndex;
158 * Sets the viewerSorter.
160 * @param viewerSorter
161 * The viewerSorter to set
163 public void setViewerSorter(ViewerSorter viewerSorter) {
164 fViewerSorter = viewerSorter;
168 * Sets the viewerSorter.
170 * @param viewerSorter
171 * The viewerSorter to set
173 public void setTreeExpansionLevel(int level) {
174 fTreeExpandLevel = level;
176 fTree.expandToLevel(level);
180 // ------ adapter communication
182 private void buttonPressed(int index) {
183 if (!managedButtonPressed(index) && fTreeAdapter != null) {
184 fTreeAdapter.customButtonPressed(this, index);
189 * Checks if the button pressed is handled internally
191 * @return Returns true if button has been handled.
193 protected boolean managedButtonPressed(int index) {
194 if (index == fRemoveButtonIndex) {
196 } else if (index == fUpButtonIndex) {
198 } else if (index == fDownButtonIndex) {
206 // ------ layout helpers
209 * @see DialogField#doFillIntoGrid
211 public Control[] doFillIntoGrid(Composite parent, int nColumns) {
212 PixelConverter converter = new PixelConverter(parent);
214 assertEnoughColumns(nColumns);
216 Label label = getLabelControl(parent);
217 GridData gd = gridDataForLabel(1);
218 gd.verticalAlignment = GridData.BEGINNING;
219 label.setLayoutData(gd);
221 Control list = getTreeControl(parent);
223 gd.horizontalAlignment = GridData.FILL;
224 gd.grabExcessHorizontalSpace = false;
225 gd.verticalAlignment = GridData.FILL;
226 gd.grabExcessVerticalSpace = true;
227 gd.horizontalSpan = nColumns - 2;
228 gd.widthHint = converter.convertWidthInCharsToPixels(50);
229 gd.heightHint = converter.convertHeightInCharsToPixels(6);
231 list.setLayoutData(gd);
233 Composite buttons = getButtonBox(parent);
235 gd.horizontalAlignment = GridData.FILL;
236 gd.grabExcessHorizontalSpace = false;
237 gd.verticalAlignment = GridData.FILL;
238 gd.grabExcessVerticalSpace = true;
239 gd.horizontalSpan = 1;
240 buttons.setLayoutData(gd);
242 return new Control[] { label, list, buttons };
246 * @see DialogField#getNumberOfControls
248 public int getNumberOfControls() {
253 * Sets the minimal width of the buttons. Must be called after widget
256 public void setButtonsMinWidth(int minWidth) {
257 if (fLastSeparator != null) {
258 ((GridData) fLastSeparator.getLayoutData()).widthHint = minWidth;
262 // ------ ui creation
265 * Returns the tree control. When called the first time, the control will be
269 * parent composite when called the first time, or
270 * <code>null</code> after.
272 public Control getTreeControl(Composite parent) {
273 if (fTreeControl == null) {
274 assertCompositeNotNull(parent);
276 fTree = createTreeViewer(parent);
278 fTreeControl = (Tree) fTree.getControl();
279 fTreeControl.addKeyListener(new KeyAdapter() {
280 public void keyPressed(KeyEvent e) {
284 fTree.setAutoExpandLevel(99);
285 fTree.setContentProvider(fTreeViewerAdapter);
286 fTree.setLabelProvider(fLabelProvider);
287 fTree.addSelectionChangedListener(fTreeViewerAdapter);
288 fTree.addDoubleClickListener(fTreeViewerAdapter);
290 fTree.setInput(fParentElement);
291 fTree.expandToLevel(fTreeExpandLevel);
293 if (fViewerSorter != null) {
294 fTree.setSorter(fViewerSorter);
297 fTreeControl.setEnabled(isEnabled());
298 if (fSelectionWhenEnabled != null) {
299 postSetSelection(fSelectionWhenEnabled);
306 * Returns the internally used table viewer.
308 public TreeViewer getTreeViewer() {
313 * Subclasses may override to specify a different style.
315 protected int getTreeStyle() {
316 int style = SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL;
320 protected TreeViewer createTreeViewer(Composite parent) {
321 Tree tree = new Tree(parent, getTreeStyle());
322 return new TreeViewer(tree);
325 protected Button createButton(Composite parent, String label,
326 SelectionListener listener) {
327 Button button = new Button(parent, SWT.PUSH);
328 button.setText(label);
329 button.addSelectionListener(listener);
330 GridData gd = new GridData();
331 gd.horizontalAlignment = GridData.FILL;
332 gd.grabExcessHorizontalSpace = true;
333 gd.verticalAlignment = GridData.BEGINNING;
334 gd.heightHint = SWTUtil.getButtonHeightHint(button);
335 gd.widthHint = SWTUtil.getButtonWidthHint(button);
337 button.setLayoutData(gd);
341 private Label createSeparator(Composite parent) {
342 Label separator = new Label(parent, SWT.NONE);
343 separator.setVisible(false);
344 GridData gd = new GridData();
345 gd.horizontalAlignment = GridData.FILL;
346 gd.verticalAlignment = GridData.BEGINNING;
348 separator.setLayoutData(gd);
353 * Returns the composite containing the buttons. When called the first time,
354 * the control will be created.
357 * parent composite when called the first time, or
358 * <code>null</code> after.
360 public Composite getButtonBox(Composite parent) {
361 if (fButtonsControl == null) {
362 assertCompositeNotNull(parent);
364 SelectionListener listener = new SelectionListener() {
365 public void widgetDefaultSelected(SelectionEvent e) {
369 public void widgetSelected(SelectionEvent e) {
374 Composite contents = new Composite(parent, SWT.NULL);
375 GridLayout layout = new GridLayout();
376 layout.marginWidth = 0;
377 layout.marginHeight = 0;
378 contents.setLayout(layout);
380 if (fButtonLabels != null) {
381 fButtonControls = new Button[fButtonLabels.length];
382 for (int i = 0; i < fButtonLabels.length; i++) {
383 String currLabel = fButtonLabels[i];
384 if (currLabel != null) {
385 fButtonControls[i] = createButton(contents, currLabel,
387 fButtonControls[i].setEnabled(isEnabled()
388 && fButtonsEnabled[i]);
390 fButtonControls[i] = null;
391 createSeparator(contents);
396 fLastSeparator = createSeparator(contents);
399 fButtonsControl = contents;
402 return fButtonsControl;
405 private void doButtonSelected(SelectionEvent e) {
406 if (fButtonControls != null) {
407 for (int i = 0; i < fButtonControls.length; i++) {
408 if (e.widget == fButtonControls[i]) {
417 * Handles key events in the table viewer. Specifically when the delete key
420 protected void handleKeyPressed(KeyEvent event) {
421 if (event.character == SWT.DEL && event.stateMask == 0) {
422 if (fRemoveButtonIndex != -1
423 && isButtonEnabled(fTree.getSelection(), fRemoveButtonIndex)) {
424 managedButtonPressed(fRemoveButtonIndex);
428 fTreeAdapter.keyPressed(this, event);
431 // ------ enable / disable management
434 * @see DialogField#dialogFieldChanged
436 public void dialogFieldChanged() {
437 super.dialogFieldChanged();
442 * Updates the enable state of the all buttons
444 protected void updateButtonState() {
445 if (fButtonControls != null) {
446 ISelection sel = fTree.getSelection();
447 for (int i = 0; i < fButtonControls.length; i++) {
448 Button button = fButtonControls[i];
449 if (isOkToUse(button)) {
450 button.setEnabled(isButtonEnabled(sel, i));
456 protected boolean containsAttributes(List selected) {
457 for (int i = 0; i < selected.size(); i++) {
458 if (!fElements.contains(selected.get(i))) {
465 protected boolean getManagedButtonState(ISelection sel, int index) {
466 List selected = getSelectedElements();
467 boolean hasAttributes = containsAttributes(selected);
468 if (index == fRemoveButtonIndex) {
469 return !selected.isEmpty() && !hasAttributes;
470 } else if (index == fUpButtonIndex) {
471 return !sel.isEmpty() && !hasAttributes && canMoveUp(selected);
472 } else if (index == fDownButtonIndex) {
473 return !sel.isEmpty() && !hasAttributes && canMoveDown(selected);
479 * @see DialogField#updateEnableState
481 protected void updateEnableState() {
482 super.updateEnableState();
484 boolean enabled = isEnabled();
485 if (isOkToUse(fTreeControl)) {
487 fSelectionWhenEnabled = fTree.getSelection();
488 selectElements(null);
490 selectElements(fSelectionWhenEnabled);
491 fSelectionWhenEnabled = null;
493 fTreeControl.setEnabled(enabled);
499 * Sets a button enabled or disabled.
501 public void enableButton(int index, boolean enable) {
502 if (fButtonsEnabled != null && index < fButtonsEnabled.length) {
503 fButtonsEnabled[index] = enable;
508 private boolean isButtonEnabled(ISelection sel, int index) {
509 boolean extraState = getManagedButtonState(sel, index);
510 return isEnabled() && extraState && fButtonsEnabled[index];
513 // ------ model access
516 * Sets the elements shown in the list.
518 public void setElements(List elements) {
519 fElements = new ArrayList(elements);
522 fTree.expandToLevel(fTreeExpandLevel);
524 dialogFieldChanged();
528 * Gets the elements shown in the list. The list returned is a copy, so it
529 * can be modified by the user.
531 public List getElements() {
532 return new ArrayList(fElements);
536 * Gets the element shown at the given index.
538 public Object getElement(int index) {
539 return fElements.get(index);
543 * Gets the index of an element in the list or -1 if element is not in list.
545 public int getIndexOfElement(Object elem) {
546 return fElements.indexOf(elem);
550 * Replace an element.
552 public void replaceElement(Object oldElement, Object newElement)
553 throws IllegalArgumentException {
554 int idx = fElements.indexOf(oldElement);
556 fElements.set(idx, newElement);
558 List selected = getSelectedElements();
559 if (selected.remove(oldElement)) {
560 selected.add(newElement);
562 boolean isExpanded = fTree.getExpandedState(oldElement);
563 fTree.remove(oldElement);
564 fTree.add(fParentElement, newElement);
566 fTree.expandToLevel(newElement, fTreeExpandLevel);
568 selectElements(new StructuredSelection(selected));
570 dialogFieldChanged();
572 throw new IllegalArgumentException();
577 * Adds an element at the end of the tree list.
579 public void addElement(Object element) {
580 if (fElements.contains(element)) {
583 fElements.add(element);
585 fTree.add(fParentElement, element);
586 fTree.expandToLevel(element, fTreeExpandLevel);
588 dialogFieldChanged();
592 * Adds elements at the end of the tree list.
594 public void addElements(List elements) {
595 int nElements = elements.size();
599 ArrayList elementsToAdd = new ArrayList(nElements);
601 for (int i = 0; i < nElements; i++) {
602 Object elem = elements.get(i);
603 if (!fElements.contains(elem)) {
604 elementsToAdd.add(elem);
607 fElements.addAll(elementsToAdd);
609 fTree.add(fParentElement, elementsToAdd.toArray());
610 for (int i = 0; i < elementsToAdd.size(); i++) {
611 fTree.expandToLevel(elementsToAdd.get(i), fTreeExpandLevel);
614 dialogFieldChanged();
619 * Adds an element at a position.
621 public void insertElementAt(Object element, int index) {
622 if (fElements.contains(element)) {
625 fElements.add(index, element);
627 fTree.add(fParentElement, element);
628 if (fTreeExpandLevel != -1) {
629 fTree.expandToLevel(element, fTreeExpandLevel);
633 dialogFieldChanged();
637 * Adds an element at a position.
639 public void removeAllElements() {
640 if (fElements.size() > 0) {
643 dialogFieldChanged();
648 * Removes an element from the list.
650 public void removeElement(Object element) throws IllegalArgumentException {
651 if (fElements.remove(element)) {
653 fTree.remove(element);
655 dialogFieldChanged();
657 throw new IllegalArgumentException();
662 * Removes elements from the list.
664 public void removeElements(List elements) {
665 if (elements.size() > 0) {
666 fElements.removeAll(elements);
668 fTree.remove(elements.toArray());
670 dialogFieldChanged();
675 * Gets the number of elements
677 public int getSize() {
678 return fElements.size();
681 public void selectElements(ISelection selection) {
682 fSelectionWhenEnabled = selection;
684 fTree.setSelection(selection, true);
688 public void selectFirstElement() {
689 Object element = null;
690 if (fViewerSorter != null) {
691 Object[] arr = fElements.toArray();
692 fViewerSorter.sort(fTree, arr);
693 if (arr.length > 0) {
697 if (fElements.size() > 0) {
698 element = fElements.get(0);
701 if (element != null) {
702 selectElements(new StructuredSelection(element));
706 public void postSetSelection(final ISelection selection) {
707 if (isOkToUse(fTreeControl)) {
708 Display d = fTreeControl.getDisplay();
709 d.asyncExec(new Runnable() {
711 if (isOkToUse(fTreeControl)) {
712 selectElements(selection);
720 * Refreshes the tree.
722 public void refresh() {
729 * Refreshes the tree.
731 public void refresh(Object element) {
733 fTree.refresh(element);
737 // ------- list maintenance
739 private List moveUp(List elements, List move) {
740 int nElements = elements.size();
741 List res = new ArrayList(nElements);
742 Object floating = null;
743 for (int i = 0; i < nElements; i++) {
744 Object curr = elements.get(i);
745 if (move.contains(curr)) {
748 if (floating != null) {
754 if (floating != null) {
760 private void moveUp(List toMoveUp) {
761 if (toMoveUp.size() > 0) {
762 setElements(moveUp(fElements, toMoveUp));
763 fTree.reveal(toMoveUp.get(0));
767 private void moveDown(List toMoveDown) {
768 if (toMoveDown.size() > 0) {
769 setElements(reverse(moveUp(reverse(fElements), toMoveDown)));
770 fTree.reveal(toMoveDown.get(toMoveDown.size() - 1));
774 private List reverse(List p) {
775 List reverse = new ArrayList(p.size());
776 for (int i = p.size() - 1; i >= 0; i--) {
777 reverse.add(p.get(i));
782 private void remove() {
783 removeElements(getSelectedElements());
787 moveUp(getSelectedElements());
790 private void down() {
791 moveDown(getSelectedElements());
794 private boolean canMoveUp(List selectedElements) {
795 if (isOkToUse(fTreeControl)) {
796 int nSelected = selectedElements.size();
797 int nElements = fElements.size();
798 for (int i = 0; i < nElements && nSelected > 0; i++) {
799 if (!selectedElements.contains(fElements.get(i))) {
808 private boolean canMoveDown(List selectedElements) {
809 if (isOkToUse(fTreeControl)) {
810 int nSelected = selectedElements.size();
811 for (int i = fElements.size() - 1; i >= 0 && nSelected > 0; i--) {
812 if (!selectedElements.contains(fElements.get(i))) {
822 * Returns the selected elements.
824 public List getSelectedElements() {
825 ArrayList result = new ArrayList();
827 ISelection selection = fTree.getSelection();
828 if (selection instanceof IStructuredSelection) {
829 Iterator iter = ((IStructuredSelection) selection).iterator();
830 while (iter.hasNext()) {
831 result.add(iter.next());
838 public void expandElement(Object element, int level) {
840 fTree.expandToLevel(element, level);
844 // ------- TreeViewerAdapter
846 private class TreeViewerAdapter implements ITreeContentProvider,
847 ISelectionChangedListener, IDoubleClickListener {
849 private final Object[] NO_ELEMENTS = new Object[0];
851 // ------- ITreeContentProvider Interface ------------
853 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
857 public boolean isDeleted(Object element) {
861 public void dispose() {
864 public Object[] getElements(Object obj) {
865 return fElements.toArray();
868 public Object[] getChildren(Object element) {
869 if (fTreeAdapter != null) {
870 return fTreeAdapter.getChildren(TreeListDialogField.this,
876 public Object getParent(Object element) {
877 if (!fElements.contains(element) && fTreeAdapter != null) {
879 .getParent(TreeListDialogField.this, element);
881 return fParentElement;
884 public boolean hasChildren(Object element) {
885 if (fTreeAdapter != null) {
886 return fTreeAdapter.hasChildren(TreeListDialogField.this,
892 // ------- ISelectionChangedListener Interface ------------
894 public void selectionChanged(SelectionChangedEvent event) {
895 doListSelected(event);
901 * @see org.eclipse.jface.viewers.IDoubleClickListener#doubleClick(org.eclipse.jface.viewers.DoubleClickEvent)
903 public void doubleClick(DoubleClickEvent event) {
904 doDoubleClick(event);
909 protected void doListSelected(SelectionChangedEvent event) {
911 if (fTreeAdapter != null) {
912 fTreeAdapter.selectionChanged(this);
916 protected void doDoubleClick(DoubleClickEvent event) {
917 if (fTreeAdapter != null) {
918 fTreeAdapter.doubleClicked(this);