/*******************************************************************************
* 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.ui.ridgets.swt;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.BeansObservables;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.swt.graphics.Image;
import org.eclipse.riena.internal.ui.ridgets.swt.ActionObserver;
import org.eclipse.riena.ui.core.marker.DisabledMarker;
import org.eclipse.riena.ui.core.resource.IIconManager;
import org.eclipse.riena.ui.core.resource.IconManagerProvider;
import org.eclipse.riena.ui.core.resource.IconSize;
import org.eclipse.riena.ui.ridgets.IActionListener;
import org.eclipse.riena.ui.ridgets.IActionRidget;
import org.eclipse.riena.ui.ridgets.IMarkableRidget;
import org.eclipse.riena.ui.ridgets.IRidget;
import org.eclipse.riena.ui.ridgets.IToggleButtonRidget;
import org.eclipse.riena.ui.swt.utils.SwtUtilities;
/**
*
*/
public abstract class AbstractToggleButtonRidget extends AbstractValueRidget implements IToggleButtonRidget {
/**
* This property is used by the databinding to sync ridget and model. It is always fired before its sibling {@link IToggleButtonRidget#PROPERTY_SELECTED} to
* ensure that the model is updated before any listeners try accessing it.
* <p>
* This property is not API. Do not use in client code.
*/
private static final String PROPERTY_SELECTED_INTERNAL = "selectedInternal"; //$NON-NLS-1$
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private final ActionObserver actionObserver;
private Binding controlBinding;
private String text;
private String iconID;
private IconSize iconSize;
private boolean selected;
private boolean textAlreadyInitialized;
private boolean useRidgetIcon;
private DisabledMarker outputAndDeselected;
public AbstractToggleButtonRidget() {
super();
actionObserver = new ActionObserver(this);
textAlreadyInitialized = false;
useRidgetIcon = false;
addPropertyChangeListener(IRidget.PROPERTY_ENABLED, new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent evt) {
propertyEnabledChanged(evt);
}
});
addPropertyChangeListener(IMarkableRidget.PROPERTY_MARKER, new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent evt) {
updateEnabledMarker();
updateEnabled();
}
});
addPropertyChangeListener(IToggleButtonRidget.PROPERTY_SELECTED, new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent evt) {
updateEnabledMarker();
}
});
}
public void addListener(final IActionListener listener) {
actionObserver.addListener(listener);
}
/**
* @since 2.0
*/
public void fireAction() {
if (isVisible() && isEnabled()) {
setSelected(!isSelected());
}
}
public String getIcon() {
return this.iconID;
}
public final String getText() {
return text;
}
public boolean isSelected() {
return selected;
}
@Override
public boolean handlesDisabledMarker() {
return true;
}
@Override
protected void updateEnabled() {
if (!SwtUtilities.isDisposed(getUIControl())) {
getUIControl().setEnabled(isEnabled() && !isOutputOnly());
}
}
/**
* This method is not API. Do not use in client code.
*
* @noreference This method is not intended to be referenced by clients.
*/
public final boolean isSelectedInternal() {
return isSelected();
}
public void removeListener(final IActionListener listener) {
actionObserver.removeListener(listener);
}
public void setIcon(final String icon) {
setIcon(icon, IconSize.NONE);
}
/**
* @since 2.0
*/
public void setIcon(final String icon, final IconSize size) {
final boolean oldUseRidgetIcon = useRidgetIcon;
useRidgetIcon = true;
final String oldIconID = this.iconID;
final IIconManager manager = IconManagerProvider.getInstance().getIconManager();
this.iconID = manager.getIconID(icon, size);
this.iconSize = size;
if (hasChanged(oldIconID, iconID) || !oldUseRidgetIcon) {
updateUIIcon();
}
}
public void setSelected(final boolean selected) {
if (this.selected != selected) {
final boolean oldValue = this.selected;
this.selected = selected;
actionObserver.widgetSelected(null);
firePropertyChange(PROPERTY_SELECTED_INTERNAL, Boolean.valueOf(oldValue), Boolean.valueOf(selected));
firePropertyChange(IToggleButtonRidget.PROPERTY_SELECTED, Boolean.valueOf(oldValue), Boolean.valueOf(selected));
updateMandatoryMarkers();
}
}
/**
* This method is not API. Do not use in client code.
*
* @noreference This method is not intended to be referenced by clients.
*/
public final void setSelectedInternal(final boolean selected) {
setSelected(selected);
}
public final void setText(final String newText) {
final String oldText = this.text;
this.text = newText;
updateUIText();
firePropertyChange(IActionRidget.PROPERTY_TEXT, oldText, this.text);
}
// protected methods
////////////////////
@Override
protected void bindUIControl() {
final DataBindingContext context = getValueBindingSupport().getContext();
if (getUIControl() != null) {
controlBinding = context.bindValue(getUIControlSelectionObservable(), getRidgetObservable(),
new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE),
new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE).setBeforeSetValidator(new CancelControlUpdateWhenDisabled()));
initText();
updateUIText();
updateSelection(isEnabled());
updateUIIcon();
}
}
@Override
protected IObservableValue getRidgetObservable() {
return BeansObservables.observeValue(this, PROPERTY_SELECTED_INTERNAL);
}
/**
* {@inheritDoc}
* <p>
* Will return false if any of the following conditions are true:
* <ul>
* <li>the ridget is not enabled – via {@code ridget.setEnabled(false)}</li>
* <li>the ridget is output only and not selected</li>
* </ul>
*/
private void updateEnabledMarker() {
if (outputAndDeselected == null) {
outputAndDeselected = new DisabledMarker(false);
outputAndDeselected.setAttribute("reason", "output-only and not-selected"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (isOutputOnly() && !isSelected()) {
addMarker(outputAndDeselected);
} else {
removeMarker(outputAndDeselected);
}
}
@Override
protected void unbindUIControl() {
super.unbindUIControl();
if (controlBinding != null) {
controlBinding.dispose();
controlBinding = null;
}
}
/**
* This method is called when a {@link PropertyChangeEvent} is received for the property {@link IRidget#PROPERTY_ENABLED}
*
* @param evt
* the event, received by the {@link PropertyChangeListener}
* @see IRidget#PROPERTY_ENABLED
* @since 5.0
*/
protected void propertyEnabledChanged(final PropertyChangeEvent evt) {
final boolean isEnabled = ((Boolean) evt.getNewValue()).booleanValue();
updateSelection(isEnabled);
}
// protected abstract methods
/////////////////////////////
/**
* @since 3.0
*/
protected abstract IObservableValue getUIControlSelectionObservable();
protected abstract String getUIControlText();
protected abstract void setUIControlImage(Image image);
protected abstract void setUIControlSelection(boolean selected);
protected abstract void setUIControlText(String text);
/**
* Updates the mandatory marker state in this ridget and it's siblings (i.e. other ToggleButtonRidgets for Buttons in the same composite).
*
* @since 3.0
*/
protected abstract void updateMandatoryMarkers();
// helping methods
//////////////////
/**
* If the text of the ridget has no value, initialize it with the text of the UI control.
*/
private void initText() {
if ((text == null) && (!textAlreadyInitialized)) {
if ((getUIControl()) != null && !(getUIControl().isDisposed())) {
text = getUIControlText();
if (text == null) {
text = EMPTY_STRING;
}
textAlreadyInitialized = true;
}
}
}
/**
* Update the selection state of this ridget's control (button)
*
* @param isRidgetEnabled
* true if this ridget is enabled, false otherwise
*/
private void updateSelection(final boolean isRidgetEnabled) {
if (getUIControl() != null && MarkerSupport.isHideDisabledRidgetContent()) {
if (!isRidgetEnabled) {
setUIControlSelection(false);
} else {
setUIControlSelection(isSelected());
}
}
}
/**
* Updates the images of the control.
*/
private void updateUIIcon() {
if (getUIControl() != null) {
Image image = null;
if (getIcon() != null) {
image = getManagedImage(getIcon(), iconSize);
}
if ((image != null) || useRidgetIcon) {
setUIControlImage(image);
}
}
}
private void updateUIText() {
if (getUIControl() != null) {
setUIControlText(text);
}
}
// helping classes
//////////////////
/**
* When the ridget is disabled, this validator will prevent the selected attribute of a control (Button) from changing -- unless
* HIDE_DISABLED_RIDGET_CONTENT is {@code false}.
*/
private final class CancelControlUpdateWhenDisabled implements IValidator {
public IStatus validate(final Object value) {
final boolean cancel = MarkerSupport.isHideDisabledRidgetContent() && !isEnabled();
return cancel ? Status.CANCEL_STATUS : Status.OK_STATUS;
}
}
}