/*******************************************************************************
* 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 static org.eclipse.riena.ui.swt.utils.SwtUtilities.*;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.databinding.BindingException;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.internal.databinding.property.value.SimplePropertyObservableValue;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.internal.databinding.swt.ButtonSelectionProperty;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Control;
import org.eclipse.riena.core.util.ReflectionUtils;
import org.eclipse.riena.ui.ridgets.IToggleButtonRidget;
import org.eclipse.riena.ui.ridgets.swt.AbstractToggleButtonRidget;
import org.eclipse.riena.ui.swt.utils.SWTBindingPropertyLocator;
/**
* Adapter of the SWT Widget <code>Button</code> with the style SWT.CHECK,
* SWT.RADIO or SWT.TOGGLE.
*/
// Note: TBR and ATBR could be merged - TBR is the only subclass
public class ToggleButtonRidget extends AbstractToggleButtonRidget {
/**
* ToggleButtonRidgets use this property to store a reference to themselves
* in their assigned control.
*/
private static final String TOGGLE_BUTTON_RIDGET = "tbr"; //$NON-NLS-1$
private SelectionObservableWithOutputOnly selectionObservable;
@Override
protected void bindUIControl() {
super.bindUIControl();
final Button control = getUIControl();
if (control != null) {
control.setData(TOGGLE_BUTTON_RIDGET, this);
updateMandatoryMarkers();
}
}
@Override
protected void checkUIControl(final Object uiControl) {
checkType(uiControl, Button.class);
if (uiControl != null) {
final Button button = (Button) uiControl;
if (!(hasStyle(button, SWT.CHECK) || hasStyle(button, SWT.RADIO) || hasStyle(button, SWT.TOGGLE))) {
throw new BindingException("Button must be a check box, a radio button or a toggle button"); //$NON-NLS-1$
}
}
}
@Override
public Button getUIControl() {
return (Button) super.getUIControl();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.riena.ui.ridgets.swt.AbstractToggleButtonRidget#propertyEnabledChanged(java.beans.PropertyChangeEvent)
*/
@Override
protected void propertyEnabledChanged(final PropertyChangeEvent evt) {
super.propertyEnabledChanged(evt);
// if we just got enabled, we have to "invalidate" the cached value in our observable
final boolean nu = evt.getNewValue() != null ? (Boolean) evt.getNewValue() : false;
final boolean old = evt.getNewValue() != null ? (Boolean) evt.getOldValue() : false;
if (nu && !old && selectionObservable != null) {
selectionObservable.resetCachedValue();
}
}
@Override
protected IObservableValue getUIControlSelectionObservable() {
if (selectionObservable == null) {
selectionObservable = new SelectionObservableWithOutputOnly(getUIControl());
}
return selectionObservable;
}
/**
* {@inheritDoc}
* <p>
* The disabled state for mandatory markers on this ridget is computed as
* follows:
* <ul>
* <li>if this ridget is selected, return true</li>
* <li>if an enabled sibling control (i.e. a Button of the same type –
* where type is check, radio or toggle – located in the same parent
* Composite) is checked, return true</li>
* <li>otherwise, return false</li>
* </ul>
* <p>
* Correspondingly, this method only returns a fully accurate result when
* this ridget has a bound UI-control. Without a UI-control is it not
* possible to look at the siblings when computing the result.
*
* @return true if mandatory markers should be disabled, false otherwise
*/
@Override
public boolean isDisableMandatoryMarker() {
boolean isSelected = isSelected();
if (!isSelected && getUIControl() != null) {
isSelected = siblingsAreSelected();
}
return isSelected;
}
@Override
protected void unbindUIControl() {
if (selectionObservable != null) {
selectionObservable.dispose();
selectionObservable = null;
}
super.unbindUIControl();
}
@Override
protected void setUIControlSelection(final boolean selected) {
getUIControl().setSelection(selected);
}
@Override
protected String getUIControlText() {
return getUIControl().getText();
}
@Override
protected void setUIControlText(final String text) {
getUIControl().setText(text);
}
@Override
protected void setUIControlImage(final Image image) {
getUIControl().setImage(image);
}
@Override
public void setSelected(final boolean selected) {
final Button selectedUIControl = getUIControl();
if (!isSelected() && selected && isRadioSingleSelection(selectedUIControl)) {
for (final Button sibling : getSiblings(selectedUIControl)) {
final String bindingId = SWTBindingPropertyLocator.getInstance().locateBindingProperty(sibling);
final IToggleButtonRidget siblingRidget = getController().getRidget(IToggleButtonRidget.class,
bindingId);
siblingRidget.setSelected(false);
}
}
super.setSelected(selected);
}
@Override
protected void updateMandatoryMarkers() {
final boolean disableMarker = isDisableMandatoryMarker();
final Button control = getUIControl();
if (control != null) {
final Button[] siblings = getSiblings(control);
for (final Button sibling : siblings) {
final Object ridget = sibling.getData(TOGGLE_BUTTON_RIDGET);
if (ridget instanceof ToggleButtonRidget && ((ToggleButtonRidget) ridget).getController() == this.getController()) {
((ToggleButtonRidget) ridget).disableMandatoryMarkers(disableMarker);
}
}
}
disableMandatoryMarkers(disableMarker);
}
// helping methods
//////////////////
private Button[] getSiblings(final Button control) {
final List<Button> result = new ArrayList<Button>();
final boolean isCheck = isCheck(control);
final boolean isRadio = isRadio(control);
final boolean isPush = isToggle(control);
final Control[] siblings = control.getParent().getChildren();
for (final Control candidate : siblings) {
if (candidate != control && candidate instanceof Button) {
if ((isCheck && isCheck(candidate)) || (isRadio && isRadio(candidate))
|| (isPush && isToggle(candidate))) {
result.add((Button) candidate);
}
}
}
return result.toArray(new Button[result.size()]);
}
private boolean isCheck(final Control control) {
return control != null && (control.getStyle() & SWT.CHECK) > 0;
}
private boolean isRadio(final Control control) {
return control != null && (control.getStyle() & SWT.RADIO) > 0;
}
private boolean isRadioSingleSelection(final Control control) {
return isRadio(control) && (control.getStyle() & SWT.NO_RADIO_GROUP) == 0;
}
private boolean isToggle(final Control control) {
return control != null && (control.getStyle() & SWT.TOGGLE) > 0;
}
private boolean siblingsAreSelected() {
boolean result = false;
final Button control = getUIControl();
final Button[] siblings = getSiblings(control);
for (int i = 0; !result && i < siblings.length; i++) {
final Button sibling = siblings[i];
if (sibling.isEnabled()) {
result = sibling.getSelection();
}
}
return result;
}
// helping classes
//////////////////
/**
* Custom IObservableValue that will revert selection changes when the
* ridget is output-only.
*
* @see http://bugs.eclipse.org/271762
* @see http://bugs.eclipse.org/321935
*/
private final class SelectionObservableWithOutputOnly extends SimplePropertyObservableValue implements
SelectionListener {
private final Button button;
@SuppressWarnings("restriction")
public SelectionObservableWithOutputOnly(final Button source) {
super(getValueBindingSupport().getContext().getValidationRealm(), source, new ButtonSelectionProperty());
Assert.isNotNull(source);
this.button = source;
this.button.addSelectionListener(this);
}
/**
* Resets the superclass field "cachedValue". Alternatively, it would be sufficient to set the field to <code>null</code> by reflection, but we want to avoid such a hack.
*
* @see SimplePropertyObservableValue
* @see ReflectionUtils#setHidden(Object, String, Object)
*/
private void resetCachedValue() {
super.doGetValue();
}
@Override
protected Object doGetValue() {
return isOutputOnly() ? Boolean.valueOf(isSelected()) : super.doGetValue();
}
public void widgetSelected(final org.eclipse.swt.events.SelectionEvent e) {
if (isOutputOnly()) {
button.setSelection(isSelected());
}
}
public void widgetDefaultSelected(final org.eclipse.swt.events.SelectionEvent e) {
// unused
}
@Override
public synchronized void dispose() {
if (!button.isDisposed()) {
button.removeSelectionListener(this);
}
}
}
}