/***************************************************************************** * Copyright (c) 2010 CEA LIST. * * 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: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation *****************************************************************************/ package org.eclipse.papyrus.infra.widgets.editors; import java.util.Arrays; import java.util.Collection; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.IChangeListener; import org.eclipse.core.databinding.observable.list.IObservableList; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.window.Window; import org.eclipse.papyrus.infra.widgets.Activator; import org.eclipse.papyrus.infra.widgets.creation.ReferenceValueFactory; import org.eclipse.papyrus.infra.widgets.messages.Messages; import org.eclipse.papyrus.infra.widgets.providers.TreeCollectionContentProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.FillLayout; 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.Tree; import org.eclipse.swt.widgets.TreeItem; /** * An editor for multivalued fields. * * @author Camille Letavernier * */ public class MultipleValueEditor extends AbstractListEditor implements SelectionListener, IChangeListener, DisposeListener { public static int MANY = -1; /** * The viewer displaying the current values from * the model */ protected TreeViewer treeViewer; /** * The tree associated to the viewer */ protected Tree tree; /** * A Composite containing the different control buttons * (Add, remove, ...) */ protected Composite controlsSection; /** * The Add control */ protected Button add; /** * The Remove control */ protected Button remove; /** * The Up control */ protected Button up; /** * The Down control */ protected Button down; /** * The edit control */ protected Button edit; /** * The Dialog displayed when adding new elements */ protected MultipleValueSelectorDialog dialog; /** * The element selector for this editor's dialog */ protected IElementSelector selector; /** * Indicates whether the underlying is ordered */ protected boolean ordered; /** * Indicates whether the underlying contains unique values */ protected boolean unique; /** * The factory for creating and editing values from * this editor */ protected ReferenceValueFactory referenceFactory; /** * Indicates if this editor is readOnly */ protected boolean readOnly; private boolean directCreation; /** * Indicates the maximum number of values selected. */ protected int upperBound; /** * * Constructor. * * @param parent * The Composite in which this Editor should be displayed * @param style * This editor's tree style * @param selector * The element selector for this editor's dialog * @param ordered * Specify if the observed collection is ordered. If true, Up and Down controls are displayed. * @param unique * Specify if the observed collection values are unique. * @param label * The label for this editor. If null, the label isn't created. */ public MultipleValueEditor(Composite parent, int style, IElementSelector selector, boolean ordered, boolean unique, String label) { this(parent, style, selector, ordered, unique, label, MANY); } /** * * Constructor. * * @param parent * The Composite in which this Editor should be displayed * @param style * This editor's tree style * @param selector * The element selector for this editor's dialog * @param ordered * Specify if the observed collection is ordered. If true, Up and Down controls are displayed. * @param unique * Specify if the observed collection values are unique. * @param label * The label for this editor. If null, the label isn't created. * @param upperBound * The maximum number of values that must appear. */ public MultipleValueEditor(Composite parent, int style, IElementSelector selector, boolean ordered, boolean unique, String label, int upperBound) { super(parent, label); Assert.isNotNull(selector, "The Element Selector must be specified for a MultipleValueEditor"); //$NON-NLS-1$ setLayout(new GridLayout(label == null ? 1 : 2, false)); controlsSection = new Composite(this, SWT.NONE); controlsSection.setLayout(new FillLayout()); controlsSection.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); tree = new Tree(this, style | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.FULL_SELECTION); GridData treeData = new GridData(SWT.FILL, SWT.FILL, true, true); treeData.horizontalSpan = 2; treeData.minimumHeight = 80; tree.setLayoutData(treeData); tree.addSelectionListener(this); treeViewer = new TreeViewer(tree); treeViewer.setContentProvider(TreeCollectionContentProvider.instance); createListControls(); this.selector = selector; dialog = createMultipleValueSelectorDialog(parent, selector, ordered, unique, label); if(label != null) { dialog.setTitle(label); } setLabelProvider(new LabelProvider()); setUpperBound(upperBound); this.ordered = ordered; this.unique = unique; updateControls(); } /** * Creates the dialog for this editor * * @param parent * The Composite in which the dialog should be displayed * @param selector * The element selector for this dialog * @param ordered * Specify if the observed collection is ordered. If true, Up and Down controls are displayed. * @param unique * Specify if the observed collection values are unique. * @param label * The editor's label. * @return The new dialog for this editor */ protected MultipleValueSelectorDialog createMultipleValueSelectorDialog(Composite parent, IElementSelector selector, boolean ordered, boolean unique, String label) { return new MultipleValueSelectorDialog(parent.getShell(), selector, label, unique, ordered); } @Override protected GridData getLabelLayoutData() { GridData data = new GridData(SWT.FILL, SWT.CENTER, true, false); return data; } public void setSelector(IElementSelector selector) { this.selector = selector; this.dialog.setSelector(selector); } protected void updateControls() { boolean enableAddAction = true; if(directCreation) { if(referenceFactory == null || !referenceFactory.canCreateObject()) { enableAddAction = false; } } add.setEnabled(!readOnly && enableAddAction); remove.setEnabled(!readOnly); up.setEnabled(ordered && !readOnly); down.setEnabled(ordered && !readOnly); edit.setEnabled(this.referenceFactory != null && referenceFactory.canEdit() && !readOnly); if(modelProperty != null && this.upperBound != MANY) { if(modelProperty.size() >= this.upperBound) { add.setEnabled(false); } } } /** * * Constructor. * * @param parent * The Composite in which this Editor should be displayed * @param style * This editor's tree style * @param selector * The element selector for this editor's dialog * @param ordered * Specify if the observed collection is ordered. If true, Up and Down controls are displayed */ public MultipleValueEditor(Composite parent, int style, IElementSelector selector, boolean ordered) { this(parent, style, selector, ordered, false, null); } /** * * Constructor. * * @param parent * The Composite in which this Editor should be displayed * @param style * This editor's tree style * @param selector * The element selector for this editor's dialog */ public MultipleValueEditor(Composite parent, int style, IElementSelector selector) { this(parent, style, selector, false, false, null); } /** * * Constructor. * * @param parent * The Composite in which this Editor should be displayed * @param style * This editor's tree style * @param selector * The element selector for this editor's dialog * @param label * The label for this Editor */ public MultipleValueEditor(Composite parent, int style, IElementSelector selector, String label) { this(parent, style, selector, false, false, label); } /** * Sets the label provider for this editor * * @param labelProvider * The label provider for this editor */ public void setLabelProvider(ILabelProvider labelProvider) { dialog.setLabelProvider(labelProvider); treeViewer.setLabelProvider(labelProvider); } /** * {@inheritDoc} */ @Override protected void doBinding() { //We don't do a real Databinding in this case treeViewer.setInput(modelProperty); modelProperty.addChangeListener(this); getParent().addDisposeListener(this); } /** * @param ordered */ public void setOrdered(boolean ordered) { this.ordered = ordered; this.dialog.setOrdered(ordered); updateControls(); } /** * @param unique */ public void setUnique(boolean unique) { this.unique = unique; this.dialog.setUnique(unique); updateControls(); } /** * Creates the Add/Remove controls, * and the Up/Down controls if the collection is ordered * * @param ordered */ protected void createListControls() { up = createButton(Activator.getDefault().getImage("/icons/Up_12x12.gif"), Messages.MultipleValueEditor_MoveSelectedElementsUp); //$NON-NLS-1$ down = createButton(Activator.getDefault().getImage("/icons/Down_12x12.gif"), Messages.MultipleValueEditor_MoveSelectedElementsDown); //$NON-NLS-1$ add = createButton(Activator.getDefault().getImage("/icons/Add_12x12.gif"), Messages.MultipleValueEditor_AddElements); //$NON-NLS-1$ remove = createButton(Activator.getDefault().getImage("/icons/Delete_12x12.gif"), Messages.MultipleValueEditor_RemoveSelectedElements); //$NON-NLS-1$ edit = createButton(Activator.getDefault().getImage("/icons/Edit_12x12.gif"), Messages.MultipleValueEditor_EditSelectedValue); //$NON-NLS-1$ } protected Button createButton(Image image, String toolTipText) { Button button = new Button(controlsSection, SWT.PUSH); button.setImage(image); button.addSelectionListener(this); button.setToolTipText(toolTipText); return button; } @Override public Object getEditableType() { return Collection.class; } /** * Handle events occuring on controls * * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent) * * @param e */ public void widgetSelected(SelectionEvent e) { if(e.widget == null) { return; } if(e.widget == add) { if(this.upperBound == MANY || modelProperty.size() < this.upperBound) { addAction(); } } else if(e.widget == remove) { removeAction(); } else if(e.widget == up) { upAction(); } else if(e.widget == down) { downAction(); } else if(e.widget == edit) { editAction(); } updateBoutons(); } /** * Handle add Action */ protected void addAction() { if(directCreation) { if(referenceFactory != null && referenceFactory.canCreateObject()) { Object newElement = referenceFactory.createObject(this); if(newElement != null) { modelProperty.add(newElement); commit(); } } return; } if(modelProperty != null) { dialog.setInitialSelections(modelProperty.toArray()); } else { dialog.setInitialSelections(new Object[0]); } int returnCode = dialog.open(); if(returnCode == Window.CANCEL) { return; } modelProperty.clear(); Object[] result = dialog.getResult(); if(result == null) { return; } modelProperty.addAll(Arrays.asList(result)); commit(); } @Override protected void commit() { super.commit(); treeViewer.refresh(); } /** * Handle remove Action */ protected void removeAction() { IStructuredSelection selection = (IStructuredSelection)treeViewer.getSelection(); for(Object value : selection.toArray()) { modelProperty.remove(value); } treeViewer.setSelection(null); commit(); } /** * Handle up Action */ protected void upAction() { IStructuredSelection selection = (IStructuredSelection)treeViewer.getSelection(); for(Object o : selection.toArray()) { int oldIndex = modelProperty.indexOf(o); if(oldIndex > 0) { modelProperty.move(oldIndex, oldIndex - 1); } } IStructuredSelection selectionCopy = new StructuredSelection(selection.toArray()); treeViewer.setSelection(selectionCopy); commit(); } /** * Handle down Action */ protected void downAction() { IStructuredSelection selection = (IStructuredSelection)treeViewer.getSelection(); int maxIndex = modelProperty.size() - 1; Object[] selectionArray = selection.toArray(); for(int i = selectionArray.length - 1; i >= 0; i--) { Object o = selectionArray[i]; int oldIndex = modelProperty.indexOf(o); if(oldIndex < maxIndex) { modelProperty.move(oldIndex, oldIndex + 1); } } IStructuredSelection selectionCopy = new StructuredSelection(selection.toArray()); treeViewer.setSelection(selectionCopy); commit(); } /** * Handle edit Action */ protected void editAction() { IStructuredSelection selection = (IStructuredSelection)treeViewer.getSelection(); if(selection.size() != 1) { return; } TreeItem selectedItem = treeViewer.getTree().getSelection()[0]; Tree parentTree = selectedItem.getParent(); int index = parentTree.indexOf(selectedItem); Object currentValue = selection.getFirstElement(); Object newValue = referenceFactory.edit(this.edit, selection.getFirstElement()); if(newValue != currentValue && newValue != null) { modelProperty.remove(index); modelProperty.add(index, newValue); //commit(); // The commit only occurs in the case where we modify the list (We don't commit direct edition on objects) } commit(); } /** * Sets the {@link ReferenceValueFactory} for this editor. The {@link ReferenceValueFactory} is used to create * new instances and edit existing ones. * * @param factory * The {@link ReferenceValueFactory} to be used by this editor */ public void setFactory(ReferenceValueFactory factory) { this.referenceFactory = factory; dialog.setFactory(factory); updateControls(); } /** * {@inheritDoc} */ public void widgetDefaultSelected(SelectionEvent e) { if(e.widget == tree && edit.isEnabled()) { editAction(); } } /** * Gets the tree viewer associated to this editor * * @return the tree viewer associated to this editor */ public TreeViewer getViewer() { return treeViewer; } /** * Refreshes the viewer when a change occurs on the ObservableList * TODO : Problem : a change occurring on an element of the list is not sent here * TODO : When undoing a command, the change event is not received (Although it modifies the list itself) * * @see org.eclipse.core.databinding.observable.IChangeListener#handleChange(org.eclipse.core.databinding.observable.ChangeEvent) * * @param event */ public void handleChange(ChangeEvent event) { treeViewer.refresh(); } /** * {@inheritDoc} */ public void widgetDisposed(org.eclipse.swt.events.DisposeEvent e) { dispose(); modelProperty.removeChangeListener(this); } /** * {@inheritDoc} */ @Override public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; updateControls(); // tree.setEnabled(!readOnly); } /** * {@inheritDoc} */ @Override public boolean isReadOnly() { return !tree.isEnabled(); } /** * {@inheritDoc} */ @Override public void setLabel(String label) { if(this.label == null) { setLayout(new GridLayout(2, false)); } super.setLabel(label); dialog.setTitle(label); } @Override public void setToolTipText(String text) { tree.setToolTipText(text); super.setLabelToolTipText(text); } @Override public void setModelObservable(IObservableList modelProperty) { super.setModelObservable(modelProperty); updateControls(); } @Override public void refreshValue() { treeViewer.refresh(); } /** * Sets the direct creation mode. * If direct creation is set to true, the {@link ReferenceValueFactory#createObject(org.eclipse.swt.widgets.Control)} method will be called when * to add button is pressed. * Otherwise, the dialog will be used. * * @param directCreation */ public void setDirectCreation(boolean directCreation) { this.directCreation = directCreation; updateControls(); } /** * Adds a ISelectionChangedListener to this widget * * @param listener */ public void addSelectionChangedListener(ISelectionChangedListener listener) { treeViewer.addSelectionChangedListener(listener); } /** * Removes a ISelectionChangedListener from this widget * * @param listener */ public void removeSelectionChangedListener(ISelectionChangedListener listener) { treeViewer.removeSelectionChangedListener(listener); } /** * Set the maximum number of values selected. * * @param upperBound */ public void setUpperBound(int upperBound) { this.upperBound = upperBound; dialog.setUpperBound(upperBound); } public void updateBoutons() { /* Disable the bouton 'add' if the upperBound is reached */ if(this.upperBound != MANY) { if(modelProperty.size() >= this.upperBound) { add.setEnabled(false); } else { add.setEnabled(true); } } } }