/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.internal.ui.ridgets.swt; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.eclipse.core.databinding.Binding; import org.eclipse.core.databinding.BindingException; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.UpdateListStrategy; import org.eclipse.core.databinding.UpdateValueStrategy; import org.eclipse.core.databinding.beans.BeansObservables; import org.eclipse.core.databinding.beans.PojoObservables; 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.databinding.observable.list.WritableList; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.core.runtime.Assert; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.riena.core.util.ListenerList; import org.eclipse.riena.ui.ridgets.IChoiceRidget; import org.eclipse.riena.ui.ridgets.IElementComparer; import org.eclipse.riena.ui.ridgets.IMarkableRidget; import org.eclipse.riena.ui.ridgets.IRidget; import org.eclipse.riena.ui.ridgets.ISingleChoiceRidget; import org.eclipse.riena.ui.ridgets.listener.ISelectionListener; import org.eclipse.riena.ui.ridgets.swt.AbstractSWTRidget; import org.eclipse.riena.ui.ridgets.swt.MarkerSupport; import org.eclipse.riena.ui.swt.ChoiceComposite; /** * Ridget for a {@link ChoiceComposite} widget with single selection. */ public class SingleChoiceRidget extends AbstractChoiceRidget implements ISingleChoiceRidget { /** The selected option. */ private final WritableValue selectionObservable; private Binding optionsBinding; private Binding selectionBinding; private Object emptySelectionItem; /** A list of selection listeners. */ private ListenerList<ISelectionListener> selectionListeners; private IElementComparer comparer; public SingleChoiceRidget() { selectionObservable = new WritableValue(); selectionObservable.addChangeListener(new IChangeListener() { public void handleChange(final ChangeEvent event) { disableMandatoryMarkers(hasMandatoryInput()); } }); addPropertyChangeListener(IRidget.PROPERTY_ENABLED, new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { updateSelection(getUIControl()); } }); addPropertyChangeListener(IMarkableRidget.PROPERTY_OUTPUT_ONLY, new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { final boolean isOutput = ((Boolean) evt.getNewValue()).booleanValue(); updateEditable(getUIControl(), !isOutput); } }); } @Override protected void bindUIControl() { if (optionsBinding != null) { createChildren(getUIControl()); } } @Override protected void checkUIControl(final Object uiControl) { checkType(uiControl, ChoiceComposite.class); if (uiControl != null) { final ChoiceComposite composite = (ChoiceComposite) uiControl; Assert.isTrue(!composite.isMultipleSelection(), "expected single selection ChoiceComposite"); //$NON-NLS-1$ } } @Override protected void unbindUIControl() { super.unbindUIControl(); disposeChildren(getUIControl()); } // public methods // /////////////// @Override public ChoiceComposite getUIControl() { return (ChoiceComposite) super.getUIControl(); } public void bindToModel(final IObservableList optionValues, final IObservableValue selectionValue) { Assert.isNotNull(optionValues, "optionValues"); //$NON-NLS-1$ Assert.isNotNull(selectionValue, "selectionValue"); //$NON-NLS-1$ bindToModel(optionValues, null, selectionValue); } public void bindToModel(final Object listHolder, final String listPropertyName, final Object selectionHolder, final String selectionPropertyName) { Assert.isNotNull(listHolder, "listHolder"); //$NON-NLS-1$ Assert.isNotNull(listPropertyName, "listPropertyName"); //$NON-NLS-1$ Assert.isNotNull(selectionHolder, "selectionHolder"); //$NON-NLS-1$ Assert.isNotNull(selectionPropertyName, "selectionPropertyName"); //$NON-NLS-1$ IObservableList optionValues; if (AbstractSWTRidget.isBean(listHolder.getClass())) { optionValues = BeansObservables.observeList(listHolder, listPropertyName); } else { optionValues = PojoObservables.observeList(listHolder, listPropertyName); } IObservableValue selectionValue; if (AbstractSWTRidget.isBean(selectionHolder.getClass())) { selectionValue = BeansObservables.observeValue(selectionHolder, selectionPropertyName); } else { selectionValue = PojoObservables.observeValue(selectionHolder, selectionPropertyName); } bindToModel(optionValues, null, selectionValue); } public void bindToModel(final List<? extends Object> optionValues, final List<String> optionLabels, final Object selectionHolder, final String selectionPropertyName) { Assert.isNotNull(optionValues, "optionValues"); //$NON-NLS-1$ Assert.isNotNull(selectionHolder, "selectionHolder"); //$NON-NLS-1$ Assert.isNotNull(selectionPropertyName, "selectionPropertyName"); //$NON-NLS-1$ final IObservableList list = new WritableList(optionValues, null); IObservableValue selectionValue; if (AbstractSWTRidget.isBean(selectionHolder.getClass())) { selectionValue = BeansObservables.observeValue(selectionHolder, selectionPropertyName); } else { selectionValue = PojoObservables.observeValue(selectionHolder, selectionPropertyName); } bindToModel(list, optionLabels, selectionValue); } /* * (non-Javadoc) * * @see org.eclipse.riena.ui.ridgets.IChoiceRidget#setComparer(org.eclipse.riena.ui.ridgets.IElementComparer) */ public void setComparer(final IElementComparer comparer) { this.comparer = comparer; } /* * (non-Javadoc) * * @see org.eclipse.riena.ui.ridgets.IChoiceRidget#getComparer() */ public IElementComparer getComparer() { return comparer; } /** * Checks whether a given elements is contained in a given collection. If an {@link IElementComparer} is set, it will be called to compare each collection * element to the given element. * * @param element * @param collection * @return <code>true</code> if the given element, or one that is equal to it, is contained in the collection */ protected boolean isElementContained(final Object element, final Collection<?> collection) { if (getComparer() == null) { return collection.contains(element); } else { final Iterator<?> i = collection.iterator(); while (i.hasNext()) { if (areElementsEqual(element, i.next())) { return true; } } return false; } } /** * Compares two given elements for equality. If an {@link IElementComparer} is set, it will be called to compare. * * @param element1 * @param element2 * @return <code>true</code> if the given elements are equal */ protected boolean areElementsEqual(final Object element1, final Object element2) { if (getComparer() != null) { return getComparer().equals(element1, element2); } else { return element1 != null && element1.equals(element2) || element1 == null && element2 == null; } } @Override public void updateFromModel() { assertIsBoundToModel(); super.updateFromModel(); optionsBinding.updateModelToTarget(); final Object oldSelection = selectionObservable.getValue(); selectionBinding.updateModelToTarget(); layoutNewChildren(); // remove unavailable element and re-apply selection Object newSelection = oldSelection; if (newSelection != null && !isElementContained(newSelection, optionsObservable)) { selectionObservable.setValue(null); newSelection = null; } firePropertyChange(PROPERTY_SELECTION, oldSelection, newSelection); } public Object getSelection() { return selectionObservable.getValue(); } public void setSelection(final Object candidate) { assertIsBoundToModel(); if (candidate != null && !isElementContained(candidate, optionsObservable)) { throw new BindingException("candidate not in option list: " + candidate); //$NON-NLS-1$ } final Object oldSelection = selectionObservable.getValue(); selectionObservable.setValue(candidate); updateSelection(getUIControl()); firePropertyChange(PROPERTY_SELECTION, oldSelection, candidate); } public Object getEmptySelectionItem() { return emptySelectionItem; } public void setEmptySelectionItem(final Object emptySelectionItem) { this.emptySelectionItem = emptySelectionItem; } public IObservableList getObservableList() { return optionsObservable; } @Override public final boolean isDisableMandatoryMarker() { return hasMandatoryInput(); } /** * {@inheritDoc} * * @since 1.2 */ public void addSelectionListener(final ISelectionListener selectionListener) { Assert.isNotNull(selectionListener, "selectionListener is null"); //$NON-NLS-1$ if (selectionListeners == null) { selectionListeners = new ListenerList<ISelectionListener>(ISelectionListener.class); addPropertyChangeListener(IChoiceRidget.PROPERTY_SELECTION, new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { notifySelectionListeners(Arrays.asList(evt.getOldValue()), Arrays.asList(evt.getNewValue())); } }); } selectionListeners.add(selectionListener); } /** * {@inheritDoc} * * @since 1.2 */ public void removeSelectionListener(final ISelectionListener selectionListener) { if (selectionListeners != null) { selectionListeners.remove(selectionListener); } } // helping methods // //////////////// private void assertIsBoundToModel() { if (optionsBinding == null || selectionBinding == null) { throw new BindingException("ridget not bound to model"); //$NON-NLS-1$ } } private void bindToModel(final IObservableList optionValues, final List<String> optionLabels, final IObservableValue selectionValue) { if (optionLabels != null) { final String msg = "Mismatch between number of optionValues and optionLabels"; //$NON-NLS-1$ Assert.isLegal(optionValues.size() == optionLabels.size(), msg); } unbindUIControl(); // clear observables as they may be bound to another model // must dispose old bindings first to avoid updating the old model if (optionsBinding != null) { optionsBinding.dispose(); optionsBinding = null; optionsObservable.clear(); } if (selectionBinding != null) { selectionBinding.dispose(); selectionBinding = null; selectionObservable.setValue(null); } // set up new binding final DataBindingContext dbc = new DataBindingContext(); optionsBinding = dbc.bindList(optionsObservable, optionValues, new UpdateListStrategy(UpdateListStrategy.POLICY_UPDATE), new UpdateListStrategy( UpdateListStrategy.POLICY_ON_REQUEST)); selectionBinding = dbc.bindValue(selectionObservable, selectionValue, new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE), new UpdateValueStrategy(UpdateValueStrategy.POLICY_ON_REQUEST)); if (optionLabels != null) { this.optionLabels = optionLabels.toArray(new String[optionLabels.size()]); } else { this.optionLabels = null; } bindUIControl(); } @Override protected void configureOptionButton(final Button button) { final SelectionAdapter listener = new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { final Button button = (Button) e.widget; final Object data = button.getData(); if (button.getSelection() && !isOutputOnly()) { // this is a workaround to make composite table aware of focus changes, Bug #264627 SingleChoiceRidget.this.setSelection(data); if (!button.isDisposed()) { fireFocusIn(button.getParent()); } } } }; button.addSelectionListener(listener); button.setData(CHOICE_RIDGET_LISTENER, listener); } @Override protected void unconfigureOptionButton(final Button button) { final Object data = button.getData(CHOICE_RIDGET_LISTENER); if (data instanceof SelectionListener) { button.removeSelectionListener((SelectionListener) data); } button.setData(CHOICE_RIDGET_LISTENER, null); } private void fireFocusIn(final Control control) { final Event event = new Event(); event.type = SWT.FocusIn; event.widget = control; control.notifyListeners(SWT.FocusIn, event); } private boolean hasInput() { return getSelection() != null; } private boolean hasMandatoryInput() { return hasInput() && (emptySelectionItem == null || !emptySelectionItem.equals(getSelection())); } /** * Iterates over the composite's children, disabling all buttons, except the one that has value as it's data element. If the ridget is not enabled, it may * deselect all buttons, as mandated by {@link MarkerSupport#isHideDisabledRidgetContent()}. */ @Override protected void updateSelection(final ChoiceComposite control) { final boolean canSelect = isEnabled() || !MarkerSupport.isHideDisabledRidgetContent(); if (control != null && !control.isDisposed()) { final Object value = selectionObservable.getValue(); for (final Control child : control.getChildrenButtons()) { final Button button = (Button) child; final boolean isSelected = canSelect && areElementsEqual(value, child.getData()); button.setSelection(isSelected); } } updateEditable(control, !isOutputOnly()); } private void notifySelectionListeners(final List<?> oldSelectionList, final List<?> newSelectionList) { if (selectionListeners != null) { final org.eclipse.riena.ui.ridgets.listener.SelectionEvent event = new org.eclipse.riena.ui.ridgets.listener.SelectionEvent(this, oldSelectionList, newSelectionList); for (final ISelectionListener listener : selectionListeners.getListeners()) { listener.ridgetSelected(event); } } } }