/*******************************************************************************
* 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.uibinding;
import java.lang.reflect.Modifier;
import org.eclipse.core.databinding.BindingException;
import org.eclipse.core.runtime.Assert;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DateTime;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Scale;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Slider;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.riena.core.singleton.SingletonProvider;
import org.eclipse.riena.core.util.ListenerList;
import org.eclipse.riena.internal.ui.ridgets.swt.ActionRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.BrowserRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.CComboRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.ComboRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.CompletionComboRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.CompositeRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.DateTextRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.DateTimeRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.DecimalTextRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.EmbeddedTitleBarRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.InfoFlyoutRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.LabelRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.LinkRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.ListRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.MasterDetailsRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.MenuItemRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.MenuRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.MessageBoxRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.ModuleTitleBarRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.MultipleChoiceRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.NumericTextRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.ProgressBarRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.ScaleRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.ShellRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.SingleChoiceRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.SliderRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.SpinnerRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.StatusMeterRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.StatuslineNumberRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.StatuslineRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.TableRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.TextRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.ToggleButtonRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.ToolItemRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.TreeRidget;
import org.eclipse.riena.internal.ui.ridgets.swt.TreeTableRidget;
import org.eclipse.riena.ui.ridgets.ClassRidgetMapper;
import org.eclipse.riena.ui.ridgets.IRidget;
import org.eclipse.riena.ui.ridgets.swt.ImageButtonRidget;
import org.eclipse.riena.ui.ridgets.uibinding.IControlRidgetMapper;
import org.eclipse.riena.ui.ridgets.uibinding.IMappingCondition;
import org.eclipse.riena.ui.swt.ChoiceComposite;
import org.eclipse.riena.ui.swt.CompletionCombo;
import org.eclipse.riena.ui.swt.DatePickerComposite;
import org.eclipse.riena.ui.swt.EmbeddedTitleBar;
import org.eclipse.riena.ui.swt.ImageButton;
import org.eclipse.riena.ui.swt.InfoFlyout;
import org.eclipse.riena.ui.swt.MasterDetailsComposite;
import org.eclipse.riena.ui.swt.MessageBox;
import org.eclipse.riena.ui.swt.ModuleTitleBar;
import org.eclipse.riena.ui.swt.StatusMeterWidget;
import org.eclipse.riena.ui.swt.Statusline;
import org.eclipse.riena.ui.swt.StatuslineNumber;
import org.eclipse.riena.ui.swt.utils.UIControlsFactory;
/**
* Default implementation of {@link IControlRidgetMapper} for SWT.
* <p>
* Note: for mappings between Interfaces and Ridget implementations see
* {@link ClassRidgetMapper}
*/
public final class SwtControlRidgetMapper implements IControlRidgetMapper<Object> {
private static final SingletonProvider<SwtControlRidgetMapper> SCRM = new SingletonProvider<SwtControlRidgetMapper>(
SwtControlRidgetMapper.class);
private static final int IGNORE_SWT_STYLE = -99;
private final ListenerList<Mapping> mappings = new ListenerList<Mapping>(Mapping.class);
/**
* Answer the singleton <code>SwtControlRidgetMapper</code>
*
* @return the SwtControlRidgetMapper singleton
*/
public static SwtControlRidgetMapper getInstance() {
return SCRM.getInstance();
}
private SwtControlRidgetMapper() {
initDefaultMappings();
}
/**
* Sets the default mapping of UI control-classes to a ridget-classes
*/
private void initDefaultMappings() {
addMapping(MenuItem.class, MenuItemRidget.class, new MenuItemCondition());
addMapping(MenuItem.class, MenuRidget.class, new MenuCondition());
addMapping(ToolItem.class, ToolItemRidget.class);
addMapping(Text.class, NumericTextRidget.class, new TypedTextWidgetCondition(UIControlsFactory.TYPE_NUMERIC));
addMapping(Text.class, DecimalTextRidget.class, new TypedTextWidgetCondition(UIControlsFactory.TYPE_DECIMAL));
addMapping(Text.class, DateTextRidget.class, new TypedTextWidgetCondition(UIControlsFactory.TYPE_DATE));
addMapping(DatePickerComposite.class, DateTextRidget.class);
addMapping(Text.class, TextRidget.class);
addMapping(Label.class, LabelRidget.class);
addMapping(Table.class, TableRidget.class);
addMapping(Browser.class, BrowserRidget.class);
addMapping(Button.class, ToggleButtonRidget.class, SWT.CHECK);
addMapping(Button.class, ToggleButtonRidget.class, SWT.TOGGLE);
addMapping(Button.class, ToggleButtonRidget.class, SWT.RADIO);
addMapping(ImageButton.class, ImageButtonRidget.class);
addMapping(Button.class, ActionRidget.class);
addMapping(ChoiceComposite.class, SingleChoiceRidget.class, new SingleChoiceCondition());
addMapping(ChoiceComposite.class, MultipleChoiceRidget.class, new MultipleChoiceCondition());
addMapping(Composite.class, CompositeRidget.class, new CompositeWithBindingIdCondition());
addMapping(Group.class, CompositeRidget.class, new CompositeWithBindingIdCondition());
addMapping(CCombo.class, CComboRidget.class);
addMapping(CompletionCombo.class, CompletionComboRidget.class);
addMapping(Combo.class, ComboRidget.class);
addMapping(DateTime.class, DateTimeRidget.class);
addMapping(org.eclipse.swt.widgets.List.class, ListRidget.class);
addMapping(Link.class, LinkRidget.class);
addMapping(Tree.class, TreeRidget.class, new TreeWithoutColumnsCondition());
addMapping(Tree.class, TreeTableRidget.class, new TreeWithColumnsCondition());
addMapping(MessageBox.class, MessageBoxRidget.class);
addMapping(Statusline.class, StatuslineRidget.class);
addMapping(StatuslineNumber.class, StatuslineNumberRidget.class);
addMapping(EmbeddedTitleBar.class, EmbeddedTitleBarRidget.class);
addMapping(ModuleTitleBar.class, ModuleTitleBarRidget.class);
addMapping(MasterDetailsComposite.class, MasterDetailsRidget.class);
addMapping(Scale.class, ScaleRidget.class);
addMapping(Spinner.class, SpinnerRidget.class);
addMapping(Slider.class, SliderRidget.class);
addMapping(ProgressBar.class, ProgressBarRidget.class);
addMapping(InfoFlyout.class, InfoFlyoutRidget.class);
addMapping(StatusMeterWidget.class, StatusMeterRidget.class);
addMapping(Shell.class, ShellRidget.class);
}
public void addMapping(final Class<? extends Object> controlClazz, final Class<? extends IRidget> ridgetClazz) {
final Mapping mapping = new Mapping(controlClazz, ridgetClazz);
mappings.add(mapping);
addMappingToClassRidgetMapper(ridgetClazz);
}
/**
* Adds a mapping of a UI control-class to a ridget-class. The mapping will
* only apply when the control has the specified swt style.
* <p>
* Example:
* <p>
* {@code addMapping(Button.class, ToggleButtonRidget.class, SWT.CHECK);}
* <p>
* Adding the same mapping twice has no effect (but is possible).
*
* @param controlClazz
* the class of the UI control (<code>Object</code>)
* @param ridgetClazz
* the class of the ridget
* @param swtStyle
* SWT style of the UI control (<code>Object</code>)
*/
public void addMapping(final Class<? extends Object> controlClazz, final Class<? extends IRidget> ridgetClazz,
final int swtStyle) {
final Mapping mapping = new Mapping(controlClazz, ridgetClazz, swtStyle);
mappings.add(mapping);
addMappingToClassRidgetMapper(ridgetClazz);
}
/**
* Adds a mapping of a UI control-class to a ridget-class. The mapping will
* only apply when the given condition evaluates to true.
* <p>
* Example:
* <p>
* {@code addMapping(Tree.class, TreeRidget.class, new
* TreeWithoutColumnsCondition());}
* <p>
* Adding the same mapping twice has no effect (but is possible).
*
* @param controlClazz
* the class of the UI control (<code>Object</code>)
* @param ridgetClazz
* the class of the ridget
* @param condition
* the condition to evaluate (non-null)
* @see IMappingCondition
*/
public void addMapping(final Class<? extends Object> controlClazz, final Class<? extends IRidget> ridgetClazz,
final IMappingCondition condition) {
final Mapping mapping = new Mapping(controlClazz, ridgetClazz, condition);
mappings.add(mapping);
addMappingToClassRidgetMapper(ridgetClazz);
}
private void addMappingToClassRidgetMapper(final Class<? extends IRidget> ridgetClazz) {
if (!Modifier.isAbstract(ridgetClazz.getModifiers())) {
ClassRidgetMapper.getInstance().addMapping(getPrimaryRidgetInterface(ridgetClazz), ridgetClazz);
}
}
public Class<? extends IRidget> getRidgetClass(final Class<? extends Object> controlClazz) {
for (final Mapping mapping : mappings) {
if (mapping.isMatching(controlClazz)) {
return mapping.getRidgetClazz();
}
}
throw new BindingException("No Ridget class defined for widget class " + controlClazz.getSimpleName()); //$NON-NLS-1$
}
public Class<? extends IRidget> getRidgetClass(final Object control) {
// first look for matching mappings with style or condition
// TODO: to optimize avoid double iteration over mappings
for (final Mapping mapping : mappings) {
if ((!mapping.isControlStyleIgnore() || mapping.hasCondition()) && mapping.isMatching(control)) {
return mapping.getRidgetClazz();
}
}
// then look for matching mappings without style and condition
for (final Mapping mapping : mappings) {
if (mapping.isControlStyleIgnore() && !mapping.hasCondition() && mapping.isMatching(control)) {
return mapping.getRidgetClazz();
}
}
return getRidgetClass(control.getClass());
}
/**
* Finds the Primary-Interface for a given Ridget-Class. The
* Primary-Interface is the one that is used to represent a specific Ridget
* e.g. LabelRidget => ILabelRidget. This Method tries to find a Interface
* that extends IRidget, if nothing was found in the current class, it
* searches the superclasses recursively.
*
* @param ridgetClass
* @return the Primary-Interface extending IRidget or null if nothing was
* found
*/
@SuppressWarnings("unchecked")
public Class<? extends IRidget> getPrimaryRidgetInterface(final Class<? extends IRidget> ridgetClass) {
if (ridgetClass == null || ridgetClass.isInterface()) {
return null;
}
for (final Class<?> inf : ridgetClass.getInterfaces()) {
if (IRidget.class.isAssignableFrom(inf)) {
return (Class<? extends IRidget>) inf;
}
}
return getPrimaryRidgetInterface((Class<? extends IRidget>) ridgetClass.getSuperclass());
}
// helping classes
// ////////////////
/**
* Mapping of UI control and ridget.
*/
public static final class Mapping {
private final Class<? extends Object> controlClazz;
private final Class<? extends IRidget> ridgetClazz;
private final int controlStyle;
private final IMappingCondition condition;
/**
* Create a new mapping of UI control and ridget.
*
* @param controlClazz
* the class of the UI control
* @param ridgetClazz
* the class of the ridget
*/
public Mapping(final Class<? extends Object> controlClazz, final Class<? extends IRidget> ridgetClazz) {
this(controlClazz, ridgetClazz, IGNORE_SWT_STYLE, null);
}
/**
* Create a new mapping of UI control and ridget.
*
* @param controlClazz
* the class of the UI control
* @param ridgetClazz
* the class of the ridget
* @param controlStyle
* the SWT style of the UI control
*/
public Mapping(final Class<? extends Object> controlClazz, final Class<? extends IRidget> ridgetClazz,
final int controlStyle) {
this(controlClazz, ridgetClazz, controlStyle, null);
}
/**
* Create a new mapping of UI control and ridget.
*
* @param controlClazz
* the class of the UI control
* @param ridgetClazz
* the class of the ridget
* @param condition
* a non-null {@link IMappingCondition} instance
*/
public Mapping(final Class<? extends Object> controlClazz, final Class<? extends IRidget> ridgetClazz,
final IMappingCondition condition) {
this(controlClazz, ridgetClazz, IGNORE_SWT_STYLE, condition);
Assert.isNotNull(condition);
}
private Mapping(final Class<? extends Object> controlClazz, final Class<? extends IRidget> ridgetClazz,
final int controlStyle, final IMappingCondition condition) {
this.controlClazz = controlClazz;
this.ridgetClazz = ridgetClazz;
this.controlStyle = controlStyle;
this.condition = condition;
}
/**
* Checks if this mapping is for given UI control.
*
* @param control
* the UI control-class
* @return true, if the control matches; otherwise false
*/
public boolean isMatching(final Class<? extends Object> controlClazz) {
if (isControlStyleIgnore() && condition == null) {
return getControlClazz().isAssignableFrom(controlClazz);
} else {
return false;
}
}
/**
* Checks if this mapping is for given UI control.
*
* @param control
* the UI control
* @return true, if the control matches; otherwise false
*/
public boolean isMatching(final Object control) {
if (control.getClass() != getControlClazz()) {
return false;
}
if (condition != null && !condition.isMatch(control)) {
return false;
}
if (control instanceof Widget && !isControlStyleIgnore()) {
if ((((Widget) control).getStyle() & getControlStyle()) != getControlStyle()) {
return false;
}
}
return true;
}
public Class<? extends IRidget> getRidgetClazz() {
return ridgetClazz;
}
// helping methods
// ////////////////
private boolean isControlStyleIgnore() {
return getControlStyle() == IGNORE_SWT_STYLE;
}
private boolean hasCondition() {
return getCondition() != null;
}
private Class<?> getControlClazz() {
return controlClazz;
}
private int getControlStyle() {
return controlStyle;
}
public IMappingCondition getCondition() {
return condition;
}
}
}