/***************************************************************************** * Copyright (c) 2010 CEA LIST. * * 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: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation *****************************************************************************/ package org.eclipse.papyrus.views.properties.widgets; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.IChangeListener; import org.eclipse.core.databinding.observable.IObservable; import org.eclipse.core.databinding.observable.list.IObservableList; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.papyrus.infra.widgets.editors.AbstractEditor; import org.eclipse.papyrus.infra.widgets.editors.AbstractListEditor; import org.eclipse.papyrus.infra.widgets.editors.AbstractValueEditor; import org.eclipse.papyrus.views.properties.Activator; import org.eclipse.papyrus.views.properties.contexts.Context; import org.eclipse.papyrus.views.properties.contexts.Property; import org.eclipse.papyrus.views.properties.modelelement.DataSource; import org.eclipse.papyrus.views.properties.runtime.ConfigurationManager; import org.eclipse.papyrus.views.properties.util.PropertiesUtil; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.widgets.Control; /** * An Abstract class to factorize code for PropertyEditors. PropertyEditors are * <strong>not</strong> required to extend this class, but could benefit from * its methods. * * @author Camille Letavernier */ public abstract class AbstractPropertyEditor implements IChangeListener, CustomizablePropertyEditor { /** * The qualified propertyPath. Represents the property edited by this widget */ protected String propertyPath; //Format : "DataContextElement:propertyName" /** * The DataSource representing the semantic objects */ protected DataSource input; protected boolean readOnly = false; protected boolean isEditable = true; /** * The SWT Widget (For list properties only) */ protected AbstractListEditor listEditor; /** * The SWT Widget (For single values only) */ protected AbstractValueEditor valueEditor; /** * The IObservableList representing the semantic property * (For list properties only) */ protected IObservableList observableList; /** * The IObservableValue representing the semantic property * (For single values only) */ protected IObservableValue observableValue; /** * Indicates if the editor's label should be displayed */ protected boolean showLabel = true; /** * The custom label used by this editor. If set, it replaces the property's default label */ protected String customLabel; /** * The maximum number of characters per line for wrapping descriptions */ public static int descriptionMaxCharPerLine = 200; /** * Constructor. * When using this constructor, you should explicitly call the #setEditor method. */ protected AbstractPropertyEditor() { } /** * Constructor. Constructs a new PropertyEditor with the given ListEditor * * @param editor */ protected AbstractPropertyEditor(AbstractListEditor editor) { setEditor(editor); } /** * Constructor. Constructs a new PropertyEditor with the given ValueEditor * * @param editor */ protected AbstractPropertyEditor(AbstractValueEditor editor) { setEditor(editor); } /** * Sets the ListEditor for this PropertyEditor * * @param editor */ protected void setEditor(AbstractListEditor editor) { this.listEditor = editor; addDisposeListener(editor); } /** * Sets the ValueEditor for this PropertyEditor * * @param editor */ protected void setEditor(AbstractValueEditor editor) { this.valueEditor = editor; addDisposeListener(editor); } private void addDisposeListener(AbstractEditor editor) { editor.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { if(input != null) { input.removeChangeListener(AbstractPropertyEditor.this); } } }); } /** * Checks if this editor has all the input needed to do the dataBinding. * As this editor can be instantiated through the XWT Framework, which is * based on an XML parser, there is no determinism in the order in which * the parameters are set. */ protected void checkInput() { if(propertyPath != null && input != null) { isEditable = input.isEditable(propertyPath); try { doBinding(); } catch (Exception ex) { //TODO : Handle the exception here. Display something ? Activator.log.error(ex); } updateLabel(); updateDescription(); } } /** * Binds the AbstractEditor (Either List or Value editor) to the semantic element */ protected void doBinding() { if(listEditor != null) { IObservableList inputObservableList = getInputObservableList(); if(inputObservableList != null) { listEditor.setModelObservable(inputObservableList); } } else if(valueEditor != null) { IObservableValue inputObservableValue = getInputObservableValue(); if(inputObservableValue != null) { valueEditor.setModelObservable(inputObservableValue); } } boolean isReadOnly = getReadOnly(); applyReadOnly(isReadOnly); if(input.forceRefresh(propertyPath)) { input.addChangeListener(this); } } /** * Applies the readOnly state to the editor * * @param readOnly * Indicates if this widget should be read-only */ protected void applyReadOnly(boolean readOnly) { AbstractEditor editor = getEditor(); if(editor != null) { editor.setReadOnly(readOnly); } } /** * {@inheritDoc} */ //TODO : This method handles a change on the DataSource. This should not be a ChangeEvent, as the DataSource is not an IObservable //This method should be changed, and the source of the event should be checked (Otherwise, it cannot be extended). //TODO : Remove the "final" modifier to let subclasses extend this behavior, //when the source of the event is checked. Until then, it is not safe to override this method public final void handleChange(ChangeEvent event) { //Handle the "forceRefresh" behavior when the input DataSource sends a ChangeEvent AbstractEditor editor = getEditor(); if(editor != null) { editor.refreshValue(); } } /** * Sets the property path for this PropertyEditor. * The propertyPath elements should be separated by ":" * e.g. UML:NamedElement:name * * @param path */ public void setProperty(String path) { propertyPath = path; checkInput(); updateLabel(); updateDescription(); } /** * Updates the label for this PropertyEditor. */ public void updateLabel() { String label = getLabel(); // if(input != null && propertyPath != null && input.isMandatory(propertyPath)) { // label += " *"; //$NON-NLS-1$ // } if(showLabel) { if(valueEditor != null) { valueEditor.setLabel(label); } else if(listEditor != null) { listEditor.setLabel(label); } } } /** * @return the property path for this Property editor. */ public String getProperty() { return propertyPath; } /** * Sets the input DataSource for this Property editor. * * @param input */ public void setInput(DataSource input) { this.input = input; checkInput(); } /** * @return the input DataSource for this Property editor */ public DataSource getInput() { return input; } /** * @return the formatted property name for this Property Editor */ protected String getLabel() { if(customLabel != null) { return customLabel; } Property property = getModelProperty(); if(property == null || property.getLabel() == null || property.getLabel().trim().equals("")) { //$NON-NLS-1$ return PropertiesUtil.getLabel(getLocalPropertyPath()); } return property.getLabel(); } /** * Updates the description for this PropertyEditor. * The description is the widget's ToolTipText */ protected void updateDescription() { String description = ""; //$NON-NLS-1$ Property property = getModelProperty(); if(property != null) { description = property.getDescription(); } //Append the propertyPath to the description if(description == null || description.trim().equals("")) { //$NON-NLS-1$ description = getLocalPropertyPath(); } else { description = PropertiesUtil.resizeString(description, descriptionMaxCharPerLine); description = getLocalPropertyPath() + ": " + description; } if(valueEditor != null) { valueEditor.setToolTipText(description); } else if(listEditor != null) { listEditor.setToolTipText(description); } } /** * Finds the property associated to the Editor's {@link #propertyPath} * * @return The property associated to the Editor's {@link #propertyPath} */ protected Property getModelProperty() { if(propertyPath == null) { return null; } Context context = getContext(); return ConfigurationManager.instance.getProperty(propertyPath, context); } private Context getContext() { if(input == null) { return null; } else { return input.getView().getContext(); } } /** * Marks this editor as readOnly * * @param readOnly */ public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; if(getEditor() != null) { getEditor().setReadOnly(getReadOnly()); } } /** * @return the AbstractEditor for this PropertyEditor */ public AbstractEditor getEditor() { return valueEditor == null ? listEditor : valueEditor; } /** * Tests if this editor is read-only * * @return * True if this editor is read-only */ public boolean getReadOnly() { boolean result = readOnly || !isEditable || getInputObservable() == null; return result; } /** * @return the IObservableList for this propertyEditor, or null if it is not * available */ protected IObservableList getInputObservableList() { if(observableList == null) { try { observableList = (IObservableList)input.getObservable(propertyPath); } catch (Exception ex) { Activator.log.error("Cannot find a valid IObservableList for " + propertyPath, ex); //$NON-NLS-1$ } } return observableList; } /** * @return the IObservableValue for this propertyEditor, or null if it is not * available */ protected IObservableValue getInputObservableValue() { if(observableValue == null) { try { observableValue = (IObservableValue)input.getObservable(propertyPath); } catch (Exception ex) { Activator.log.error("Cannot find a valid IObservableValue for " + propertyPath, ex); //$NON-NLS-1$ } } return observableValue; } /** * Returns the IObservable for this propertyEditor, or null if it is * not available * * @return The IObservable associated to this propertyEditor */ protected IObservable getInputObservable() { if(input == null || propertyPath == null) { return null; } if(listEditor != null) { return getInputObservableList(); } if(valueEditor != null) { return getInputObservableValue(); } return null; } /** * @return the last segment of the property path (Which is the property name) */ protected String getLocalPropertyPath() { return propertyPath.substring(propertyPath.lastIndexOf(":") + 1); //$NON-NLS-1$ } /** * Sets the editor's Layout Data * * @param data */ public void setLayoutData(Object data) { if(getEditor() != null) { getEditor().setLayoutData(data); } } /** * Returns the editor's Layout Data * * @return * The editor's layout data */ public Object getLayoutData() { return getEditor() == null ? null : getEditor().getLayoutData(); } /** * Indicates whether the editor's label should be displayed or not * * @param showLabel */ public void setShowLabel(boolean showLabel) { AbstractEditor editor = getEditor(); this.showLabel = showLabel; if(editor != null) { editor.setDisplayLabel(showLabel); } } /** * Indicates whether the editor's label is displayed or not * * @return * true if the label should be displayed */ public boolean getShowLabel() { return this.showLabel; } /** * Sets the label for this editor. The label will replace the property's * default label * * @param customLabel * The label to use with this property editor */ public void setCustomLabel(String customLabel) { this.customLabel = customLabel; updateLabel(); } /** * @return the custom label used by this property editor. May be null */ public String getCustomLabel() { return this.customLabel; } /** * @return the Control defined by this Property Editor */ public Control getControl() { if(valueEditor == null) { return listEditor; } return valueEditor; } }