/******************************************************************************* * 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.ArrayList; import java.util.Collections; 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.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.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.beans.common.ListBean; import org.eclipse.riena.core.util.ListenerList; import org.eclipse.riena.ui.ridgets.IChoiceRidget; import org.eclipse.riena.ui.ridgets.IMarkableRidget; import org.eclipse.riena.ui.ridgets.IMultipleChoiceRidget; import org.eclipse.riena.ui.ridgets.IRidget; 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 multiple selection. */ public class MultipleChoiceRidget extends AbstractChoiceRidget implements IMultipleChoiceRidget { /** The selected option. */ private final WritableList selectionObservable; private Binding optionsBinding; private Binding selectionBinding; /** A list of selection listeners. */ private ListenerList<ISelectionListener> selectionListeners; public MultipleChoiceRidget() { selectionObservable = new WritableList(); selectionObservable.addChangeListener(new IChangeListener() { public void handleChange(final ChangeEvent event) { disableMandatoryMarkers(hasInput()); } }); 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 multiple 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 IObservableList selectionValues) { Assert.isNotNull(optionValues, "optionValues"); //$NON-NLS-1$ Assert.isNotNull(selectionValues, "selectionValues"); //$NON-NLS-1$ bindToModel(optionValues, null, selectionValues); } 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); } IObservableList selectionValues; if (AbstractSWTRidget.isBean(selectionHolder.getClass())) { selectionValues = BeansObservables.observeList(selectionHolder, selectionPropertyName); } else { selectionValues = PojoObservables.observeList(selectionHolder, selectionPropertyName); } bindToModel(optionValues, null, selectionValues); } 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 optionList = PojoObservables.observeList(new ListBean(optionValues), ListBean.PROPERTY_VALUES); IObservableList selectionList; if (AbstractSWTRidget.isBean(selectionHolder.getClass())) { selectionList = BeansObservables.observeList(selectionHolder, selectionPropertyName); } else { selectionList = PojoObservables.observeList(selectionHolder, selectionPropertyName); } bindToModel(optionList, optionLabels, selectionList); } @SuppressWarnings("unchecked") @Override public void updateFromModel() { assertIsBoundToModel(); super.updateFromModel(); optionsBinding.updateModelToTarget(); final List<?> oldSelection = new ArrayList<Object>(selectionObservable); selectionBinding.updateModelToTarget(); layoutNewChildren(); // remove unavailable elements and re-apply selection for (final Object candidate : oldSelection) { if (!optionsObservable.contains(candidate)) { selectionObservable.remove(candidate); } } firePropertyChange(PROPERTY_SELECTION, oldSelection, selectionObservable); } public IObservableList getObservableSelectionList() { return selectionObservable; } @SuppressWarnings("unchecked") public List<?> getSelection() { return new ArrayList<Object>(selectionObservable); } @SuppressWarnings("unchecked") public void setSelection(final List<?> selection) { assertIsBoundToModel(); final List<?> oldSelection = new ArrayList<Object>(selectionObservable); final List<?> newSelection = selection == null ? Collections.EMPTY_LIST : selection; for (final Object candidate : newSelection) { if (!optionsObservable.contains(candidate)) { throw new BindingException("candidate not in option list: " + candidate); //$NON-NLS-1$ } } selectionObservable.clear(); selectionObservable.addAll(newSelection); updateSelection(getUIControl()); firePropertyChange(PROPERTY_SELECTION, oldSelection, newSelection); } public IObservableList getObservableList() { return optionsObservable; } @Override public boolean isDisableMandatoryMarker() { return hasInput(); } /** * {@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((List<?>) evt.getOldValue(), (List<?>) 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 IObservableList selectionValues) { 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.clear(); } // 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.bindList(selectionObservable, selectionValues, new UpdateListStrategy(UpdateListStrategy.POLICY_UPDATE), new UpdateListStrategy( UpdateListStrategy.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() { @SuppressWarnings("unchecked") @Override public void widgetSelected(final SelectionEvent e) { final Button button = (Button) e.widget; final Object data = button.getData(); if (!isOutputOnly()) { if (button.getSelection()) { if (!selectionObservable.contains(data)) { final List<?> oldSelection = new ArrayList<Object>(selectionObservable); selectionObservable.add(data); firePropertyChange(PROPERTY_SELECTION, oldSelection, selectionObservable); } } else { final List<?> oldSelection = new ArrayList<Object>(selectionObservable); selectionObservable.remove(data); firePropertyChange(PROPERTY_SELECTION, oldSelection, selectionObservable); } if (!button.isDisposed()) { // this is a workaround to make composite table aware of focus changes, Bug #264627 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 selectionObservable != null && selectionObservable.size() > 0; } /** * 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()) { for (final Control child : control.getChildrenButtons()) { final Button button = (Button) child; final boolean isSelected = canSelect && selectionObservable.contains(button.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); } } } }