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;
49 * A list with a button bar.
50 * Typical buttons are 'Add', 'Remove', 'Up' and 'Down'.
51 * List model is independend of widget creation.
52 * DialogFields controls are: Label, List and Composite containing buttons.
54 public class TreeListDialogField extends DialogField {
56 protected TreeViewer fTree;
57 protected ILabelProvider fLabelProvider;
58 protected TreeViewerAdapter fTreeViewerAdapter;
59 protected List fElements;
60 protected ViewerSorter fViewerSorter;
62 protected String[] fButtonLabels;
63 private Button[] fButtonControls;
65 private boolean[] fButtonsEnabled;
67 private int fRemoveButtonIndex;
68 private int fUpButtonIndex;
69 private int fDownButtonIndex;
71 private Label fLastSeparator;
73 private Tree fTreeControl;
74 private Composite fButtonsControl;
75 private ISelection fSelectionWhenEnabled;
77 private ITreeListAdapter fTreeAdapter;
79 private Object fParentElement;
80 private int fTreeExpandLevel;
83 * @param adapter Can be <code>null</code>.
85 public TreeListDialogField(ITreeListAdapter adapter, String[] buttonLabels, ILabelProvider lprovider) {
87 fTreeAdapter= adapter;
89 fLabelProvider= lprovider;
90 fTreeViewerAdapter= new TreeViewerAdapter();
93 fElements= new ArrayList(10);
95 fButtonLabels= buttonLabels;
96 if (fButtonLabels != null) {
97 int nButtons= fButtonLabels.length;
98 fButtonsEnabled= new boolean[nButtons];
99 for (int i= 0; i < nButtons; i++) {
100 fButtonsEnabled[i]= true;
106 fButtonsControl= null;
108 fRemoveButtonIndex= -1;
110 fDownButtonIndex= -1;
116 * Sets the index of the 'remove' button in the button label array passed in
117 * the constructor. The behaviour of the button marked as the 'remove' button
118 * will then behandled internally. (enable state, button invocation
121 public void setRemoveButtonIndex(int removeButtonIndex) {
122 Assert.isTrue(removeButtonIndex < fButtonLabels.length);
123 fRemoveButtonIndex= removeButtonIndex;
127 * Sets the index of the 'up' button in the button label array passed in the
128 * constructor. The behaviour of the button marked as the 'up' button will
129 * then behandled internally.
130 * (enable state, button invocation behaviour)
132 public void setUpButtonIndex(int upButtonIndex) {
133 Assert.isTrue(upButtonIndex < fButtonLabels.length);
134 fUpButtonIndex= upButtonIndex;
138 * Sets the index of the 'down' button in the button label array passed in
139 * the constructor. The behaviour of the button marked as the 'down' button
140 * will then be handled internally. (enable state, button invocation
143 public void setDownButtonIndex(int downButtonIndex) {
144 Assert.isTrue(downButtonIndex < fButtonLabels.length);
145 fDownButtonIndex= downButtonIndex;
149 * Sets the viewerSorter.
150 * @param viewerSorter The viewerSorter to set
152 public void setViewerSorter(ViewerSorter viewerSorter) {
153 fViewerSorter= viewerSorter;
157 * Sets the viewerSorter.
158 * @param viewerSorter The viewerSorter to set
160 public void setTreeExpansionLevel(int level) {
161 fTreeExpandLevel= level;
163 fTree.expandToLevel(level);
167 // ------ adapter communication
169 private void buttonPressed(int index) {
170 if (!managedButtonPressed(index) && fTreeAdapter != null) {
171 fTreeAdapter.customButtonPressed(this, index);
176 * Checks if the button pressed is handled internally
177 * @return Returns true if button has been handled.
179 protected boolean managedButtonPressed(int index) {
180 if (index == fRemoveButtonIndex) {
182 } else if (index == fUpButtonIndex) {
184 } else if (index == fDownButtonIndex) {
192 // ------ layout helpers
195 * @see DialogField#doFillIntoGrid
197 public Control[] doFillIntoGrid(Composite parent, int nColumns) {
198 PixelConverter converter= new PixelConverter(parent);
200 assertEnoughColumns(nColumns);
202 Label label= getLabelControl(parent);
203 GridData gd= gridDataForLabel(1);
204 gd.verticalAlignment= GridData.BEGINNING;
205 label.setLayoutData(gd);
207 Control list= getTreeControl(parent);
209 gd.horizontalAlignment= GridData.FILL;
210 gd.grabExcessHorizontalSpace= false;
211 gd.verticalAlignment= GridData.FILL;
212 gd.grabExcessVerticalSpace= true;
213 gd.horizontalSpan= nColumns - 2;
214 gd.widthHint= converter.convertWidthInCharsToPixels(50);
215 gd.heightHint= converter.convertHeightInCharsToPixels(6);
217 list.setLayoutData(gd);
219 Composite buttons= getButtonBox(parent);
221 gd.horizontalAlignment= GridData.FILL;
222 gd.grabExcessHorizontalSpace= false;
223 gd.verticalAlignment= GridData.FILL;
224 gd.grabExcessVerticalSpace= true;
225 gd.horizontalSpan= 1;
226 buttons.setLayoutData(gd);
228 return new Control[] { label, list, buttons };
232 * @see DialogField#getNumberOfControls
234 public int getNumberOfControls() {
239 * Sets the minimal width of the buttons. Must be called after widget creation.
241 public void setButtonsMinWidth(int minWidth) {
242 if (fLastSeparator != null) {
243 ((GridData) fLastSeparator.getLayoutData()).widthHint= minWidth;
247 // ------ ui creation
250 * Returns the tree control. When called the first time, the control will be
252 * @param The parent composite when called the first time, or <code>null</code>
255 public Control getTreeControl(Composite parent) {
256 if (fTreeControl == null) {
257 assertCompositeNotNull(parent);
259 fTree= createTreeViewer(parent);
261 fTreeControl= (Tree) fTree.getControl();
262 fTreeControl.addKeyListener(new KeyAdapter() {
263 public void keyPressed(KeyEvent e) {
267 fTree.setAutoExpandLevel(99);
268 fTree.setContentProvider(fTreeViewerAdapter);
269 fTree.setLabelProvider(fLabelProvider);
270 fTree.addSelectionChangedListener(fTreeViewerAdapter);
271 fTree.addDoubleClickListener(fTreeViewerAdapter);
273 fTree.setInput(fParentElement);
274 fTree.expandToLevel(fTreeExpandLevel);
276 if (fViewerSorter != null) {
277 fTree.setSorter(fViewerSorter);
280 fTreeControl.setEnabled(isEnabled());
281 if (fSelectionWhenEnabled != null) {
282 postSetSelection(fSelectionWhenEnabled);
289 * Returns the internally used table viewer.
291 public TreeViewer getTreeViewer() {
296 * Subclasses may override to specify a different style.
298 protected int getTreeStyle() {
299 int style= SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL;
303 protected TreeViewer createTreeViewer(Composite parent) {
304 Tree tree= new Tree(parent, getTreeStyle());
305 return new TreeViewer(tree);
308 protected Button createButton(Composite parent, String label, SelectionListener listener) {
309 Button button= new Button(parent, SWT.PUSH);
310 button.setText(label);
311 button.addSelectionListener(listener);
312 GridData gd= new GridData();
313 gd.horizontalAlignment= GridData.FILL;
314 gd.grabExcessHorizontalSpace= true;
315 gd.verticalAlignment= GridData.BEGINNING;
316 gd.heightHint= SWTUtil.getButtonHeigthHint(button);
317 gd.widthHint= SWTUtil.getButtonWidthHint(button);
319 button.setLayoutData(gd);
323 private Label createSeparator(Composite parent) {
324 Label separator= new Label(parent, SWT.NONE);
325 separator.setVisible(false);
326 GridData gd= new GridData();
327 gd.horizontalAlignment= GridData.FILL;
328 gd.verticalAlignment= GridData.BEGINNING;
330 separator.setLayoutData(gd);
335 * Returns the composite containing the buttons. When called the first time, the control
337 * @param The parent composite when called the first time, or <code>null</code>
340 public Composite getButtonBox(Composite parent) {
341 if (fButtonsControl == null) {
342 assertCompositeNotNull(parent);
344 SelectionListener listener= new SelectionListener() {
345 public void widgetDefaultSelected(SelectionEvent e) {
348 public void widgetSelected(SelectionEvent e) {
353 Composite contents= new Composite(parent, SWT.NULL);
354 GridLayout layout= new GridLayout();
355 layout.marginWidth= 0;
356 layout.marginHeight= 0;
357 contents.setLayout(layout);
359 if (fButtonLabels != null) {
360 fButtonControls= new Button[fButtonLabels.length];
361 for (int i= 0; i < fButtonLabels.length; i++) {
362 String currLabel= fButtonLabels[i];
363 if (currLabel != null) {
364 fButtonControls[i]= createButton(contents, currLabel, listener);
365 fButtonControls[i].setEnabled(isEnabled() && fButtonsEnabled[i]);
367 fButtonControls[i]= null;
368 createSeparator(contents);
373 fLastSeparator= createSeparator(contents);
376 fButtonsControl= contents;
379 return fButtonsControl;
382 private void doButtonSelected(SelectionEvent e) {
383 if (fButtonControls != null) {
384 for (int i= 0; i < fButtonControls.length; i++) {
385 if (e.widget == fButtonControls[i]) {
394 * Handles key events in the table viewer. Specifically
395 * when the delete key is pressed.
397 protected void handleKeyPressed(KeyEvent event) {
398 if (event.character == SWT.DEL && event.stateMask == 0) {
399 if (fRemoveButtonIndex != -1 && isButtonEnabled(fTree.getSelection(), fRemoveButtonIndex)) {
400 managedButtonPressed(fRemoveButtonIndex);
404 fTreeAdapter.keyPressed(this, event);
407 // ------ enable / disable management
410 * @see DialogField#dialogFieldChanged
412 public void dialogFieldChanged() {
413 super.dialogFieldChanged();
418 * Updates the enable state of the all buttons
420 protected void updateButtonState() {
421 if (fButtonControls != null) {
422 ISelection sel= fTree.getSelection();
423 for (int i= 0; i < fButtonControls.length; i++) {
424 Button button= fButtonControls[i];
425 if (isOkToUse(button)) {
426 button.setEnabled(isButtonEnabled(sel, i));
433 protected boolean containsAttributes(List selected) {
434 for (int i= 0; i < selected.size(); i++) {
435 if (!fElements.contains(selected.get(i))) {
443 protected boolean getManagedButtonState(ISelection sel, int index) {
444 List selected= getSelectedElements();
445 boolean hasAttributes= containsAttributes(selected);
446 if (index == fRemoveButtonIndex) {
447 return !selected.isEmpty() && !hasAttributes;
448 } else if (index == fUpButtonIndex) {
449 return !sel.isEmpty() && !hasAttributes && canMoveUp(selected);
450 } else if (index == fDownButtonIndex) {
451 return !sel.isEmpty() && !hasAttributes && canMoveDown(selected);
457 * @see DialogField#updateEnableState
459 protected void updateEnableState() {
460 super.updateEnableState();
462 boolean enabled= isEnabled();
463 if (isOkToUse(fTreeControl)) {
465 fSelectionWhenEnabled= fTree.getSelection();
466 selectElements(null);
468 selectElements(fSelectionWhenEnabled);
469 fSelectionWhenEnabled= null;
471 fTreeControl.setEnabled(enabled);
477 * Sets a button enabled or disabled.
479 public void enableButton(int index, boolean enable) {
480 if (fButtonsEnabled != null && index < fButtonsEnabled.length) {
481 fButtonsEnabled[index]= enable;
486 private boolean isButtonEnabled(ISelection sel, int index) {
487 boolean extraState= getManagedButtonState(sel, index);
488 return isEnabled() && extraState && fButtonsEnabled[index];
491 // ------ model access
494 * Sets the elements shown in the list.
496 public void setElements(List elements) {
497 fElements= new ArrayList(elements);
500 fTree.expandToLevel(fTreeExpandLevel);
502 dialogFieldChanged();
506 * Gets the elements shown in the list.
507 * The list returned is a copy, so it can be modified by the user.
509 public List getElements() {
510 return new ArrayList(fElements);
514 * Gets the element shown at the given index.
516 public Object getElement(int index) {
517 return fElements.get(index);
521 * Gets the index of an element in the list or -1 if element is not in list.
523 public int getIndexOfElement(Object elem) {
524 return fElements.indexOf(elem);
528 * Replace an element.
530 public void replaceElement(Object oldElement, Object newElement) throws IllegalArgumentException {
531 int idx= fElements.indexOf(oldElement);
533 fElements.set(idx, newElement);
535 List selected= getSelectedElements();
536 if (selected.remove(oldElement)) {
537 selected.add(newElement);
539 boolean isExpanded= fTree.getExpandedState(oldElement);
540 fTree.remove(oldElement);
541 fTree.add(fParentElement, newElement);
543 fTree.expandToLevel(newElement, fTreeExpandLevel);
545 selectElements(new StructuredSelection(selected));
547 dialogFieldChanged();
549 throw new IllegalArgumentException();
554 * Adds an element at the end of the tree list.
556 public void addElement(Object element) {
557 if (fElements.contains(element)) {
560 fElements.add(element);
562 fTree.add(fParentElement, element);
563 fTree.expandToLevel(element, fTreeExpandLevel);
565 dialogFieldChanged();
569 * Adds elements at the end of the tree list.
571 public void addElements(List elements) {
572 int nElements= elements.size();
576 ArrayList elementsToAdd= new ArrayList(nElements);
578 for (int i= 0; i < nElements; i++) {
579 Object elem= elements.get(i);
580 if (!fElements.contains(elem)) {
581 elementsToAdd.add(elem);
584 fElements.addAll(elementsToAdd);
586 fTree.add(fParentElement, elementsToAdd.toArray());
587 for (int i= 0; i < elementsToAdd.size(); i++) {
588 fTree.expandToLevel(elementsToAdd.get(i), fTreeExpandLevel);
591 dialogFieldChanged();
596 * Adds an element at a position.
598 public void insertElementAt(Object element, int index) {
599 if (fElements.contains(element)) {
602 fElements.add(index, element);
604 fTree.add(fParentElement, element);
605 if (fTreeExpandLevel != -1) {
606 fTree.expandToLevel(element, fTreeExpandLevel);
610 dialogFieldChanged();
614 * Adds an element at a position.
616 public void removeAllElements() {
617 if (fElements.size() > 0) {
620 dialogFieldChanged();
625 * Removes an element from the list.
627 public void removeElement(Object element) throws IllegalArgumentException {
628 if (fElements.remove(element)) {
630 fTree.remove(element);
632 dialogFieldChanged();
634 throw new IllegalArgumentException();
639 * Removes elements from the list.
641 public void removeElements(List elements) {
642 if (elements.size() > 0) {
643 fElements.removeAll(elements);
645 fTree.remove(elements.toArray());
647 dialogFieldChanged();
652 * Gets the number of elements
654 public int getSize() {
655 return fElements.size();
658 public void selectElements(ISelection selection) {
659 fSelectionWhenEnabled= selection;
661 fTree.setSelection(selection, true);
665 public void selectFirstElement() {
666 Object element= null;
667 if (fViewerSorter != null) {
668 Object[] arr= fElements.toArray();
669 fViewerSorter.sort(fTree, arr);
670 if (arr.length > 0) {
674 if (fElements.size() > 0) {
675 element= fElements.get(0);
678 if (element != null) {
679 selectElements(new StructuredSelection(element));
683 public void postSetSelection(final ISelection selection) {
684 if (isOkToUse(fTreeControl)) {
685 Display d= fTreeControl.getDisplay();
686 d.asyncExec(new Runnable() {
688 if (isOkToUse(fTreeControl)) {
689 selectElements(selection);
697 * Refreshes the tree.
699 public void refresh() {
706 * Refreshes the tree.
708 public void refresh(Object element) {
710 fTree.refresh(element);
714 // ------- list maintenance
716 private List moveUp(List elements, List move) {
717 int nElements= elements.size();
718 List res= new ArrayList(nElements);
719 Object floating= null;
720 for (int i= 0; i < nElements; i++) {
721 Object curr= elements.get(i);
722 if (move.contains(curr)) {
725 if (floating != null) {
731 if (floating != null) {
737 private void moveUp(List toMoveUp) {
738 if (toMoveUp.size() > 0) {
739 setElements(moveUp(fElements, toMoveUp));
740 fTree.reveal(toMoveUp.get(0));
744 private void moveDown(List toMoveDown) {
745 if (toMoveDown.size() > 0) {
746 setElements(reverse(moveUp(reverse(fElements), toMoveDown)));
747 fTree.reveal(toMoveDown.get(toMoveDown.size() - 1));
751 private List reverse(List p) {
752 List reverse= new ArrayList(p.size());
753 for (int i= p.size() - 1; i >= 0; i--) {
754 reverse.add(p.get(i));
759 private void remove() {
760 removeElements(getSelectedElements());
764 moveUp(getSelectedElements());
767 private void down() {
768 moveDown(getSelectedElements());
771 private boolean canMoveUp(List selectedElements) {
772 if (isOkToUse(fTreeControl)) {
773 int nSelected= selectedElements.size();
774 int nElements= fElements.size();
775 for (int i= 0; i < nElements && nSelected > 0; i++) {
776 if (!selectedElements.contains(fElements.get(i))) {
785 private boolean canMoveDown(List selectedElements) {
786 if (isOkToUse(fTreeControl)) {
787 int nSelected= selectedElements.size();
788 for (int i= fElements.size() - 1; i >= 0 && nSelected > 0; i--) {
789 if (!selectedElements.contains(fElements.get(i))) {
799 * Returns the selected elements.
801 public List getSelectedElements() {
802 ArrayList result= new ArrayList();
804 ISelection selection= fTree.getSelection();
805 if (selection instanceof IStructuredSelection) {
806 Iterator iter= ((IStructuredSelection)selection).iterator();
807 while (iter.hasNext()) {
808 result.add(iter.next());
815 public void expandElement(Object element, int level) {
817 fTree.expandToLevel(element, level);
822 // ------- TreeViewerAdapter
824 private class TreeViewerAdapter implements ITreeContentProvider, ISelectionChangedListener, IDoubleClickListener {
826 private final Object[] NO_ELEMENTS= new Object[0];
828 // ------- ITreeContentProvider Interface ------------
830 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
834 public boolean isDeleted(Object element) {
838 public void dispose() {
841 public Object[] getElements(Object obj) {
842 return fElements.toArray();
845 public Object[] getChildren(Object element) {
846 if (fTreeAdapter != null) {
847 return fTreeAdapter.getChildren(TreeListDialogField.this, element);
852 public Object getParent(Object element) {
853 if (!fElements.contains(element) && fTreeAdapter != null) {
854 return fTreeAdapter.getParent(TreeListDialogField.this, element);
856 return fParentElement;
859 public boolean hasChildren(Object element) {
860 if (fTreeAdapter != null) {
861 return fTreeAdapter.hasChildren(TreeListDialogField.this, element);
866 // ------- ISelectionChangedListener Interface ------------
868 public void selectionChanged(SelectionChangedEvent event) {
869 doListSelected(event);
873 * @see org.eclipse.jface.viewers.IDoubleClickListener#doubleClick(org.eclipse.jface.viewers.DoubleClickEvent)
875 public void doubleClick(DoubleClickEvent event) {
876 doDoubleClick(event);
881 protected void doListSelected(SelectionChangedEvent event) {
883 if (fTreeAdapter != null) {
884 fTreeAdapter.selectionChanged(this);
888 protected void doDoubleClick(DoubleClickEvent event) {
889 if (fTreeAdapter != null) {
890 fTreeAdapter.doubleClicked(this);