/*******************************************************************************
* 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;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.riena.core.marker.IMarker;
import org.eclipse.riena.core.marker.IMarkerAttributeChangeListener;
import org.eclipse.riena.core.marker.Markable;
import org.eclipse.riena.ui.core.marker.DisabledMarker;
import org.eclipse.riena.ui.core.marker.HiddenMarker;
import org.eclipse.riena.ui.core.marker.IMarkerPropertyChangeEvent;
import org.eclipse.riena.ui.core.marker.OutputMarker;
/**
* Helper class for Ridgets to delegate their marker issues to.
*/
public abstract class AbstractMarkerSupport {
private Collection<IMarker> markers;
private Set<Class<? extends IMarker>> hiddenMarkerTypes;
private IBasicMarkableRidget ridget;
private PropertyChangeSupport propertyChangeSupport;
private IMarkerAttributeChangeListener markerAttributeChangeListener;
/**
* @since 2.0
*/
public AbstractMarkerSupport() {
markerAttributeChangeListener = new MarkerAttributeChangeListener();
}
public AbstractMarkerSupport(final IBasicMarkableRidget ridget, final PropertyChangeSupport propertyChangeSupport) {
super();
init(ridget, propertyChangeSupport);
}
/**
* @see org.eclipse.riena.ui.internal.ridgets.IBasicMarkableRidget#addMarker(org.eclipse.riena.core.marker.IMarker)
*/
public void addMarker(final IMarker marker) {
if (marker == null) {
return;
}
initializeMarkers();
if (markers.contains(marker)) {
return;
}
final Collection<IMarker> oldValue = cloneMarkers();
if (markers.add(marker)) {
updateMarkers();
final Collection<IMarker> newValue = getMarkers();
fireMarkerPropertyChangeEvent(oldValue, newValue);
fireOutputPropertyChangeEvent(oldValue, newValue);
fireEnabledPropertyChangeEvent(oldValue, newValue);
marker.addAttributeChangeListener(markerAttributeChangeListener);
}
}
/**
* Callback method triggered when a control is bound to the {@link IRidget}.
* <p>
* The default implementation is empty. Subclasses may override.
*
* @since 3.0
*/
public void bind() {
// nop
}
/**
* Initializes this marker support with the Ridget and the property change
* support.
*
* @param ridget
* the Ridget that needs the marker support.
* @param propertyChangeSupport
* @since 2.0
*/
public void init(final IBasicMarkableRidget ridget, final PropertyChangeSupport propertyChangeSupport) {
this.ridget = ridget;
this.propertyChangeSupport = propertyChangeSupport;
}
public void fireShowingPropertyChangeEvent() {
propertyChangeSupport.firePropertyChange(IRidget.PROPERTY_SHOWING, !ridget.isVisible(), ridget.isVisible());
updateMarkers();
}
/**
* "Flashes" some kind of notification <b>asynchronously</b>. The notion and
* duration of "flashing" is implementation specific - it could flash a
* special color, an error decoration, etc.
* <p>
* The default implementation does nothing. Subclasses should override.
* Implementors shall check that the ui control is not null and that the
* flash is not already in progress.
* <p>
* <b>Flashing must not alter the marker state of the ridget!</b>
*
* @since 3.0
*/
public void flash() {
// does nothing - subclasses should override
}
/**
* Returns all hidden marker types.
*
* @return the set of currently hidden marker types; never null; may be
* empty
*
* @since 3.0
*/
@SuppressWarnings("unchecked")
public final Set<Class<IMarker>> getHiddenMarkerTypes() {
if (hiddenMarkerTypes == null) {
return Collections.EMPTY_SET;
}
final Set<Class<IMarker>> result = new HashSet<Class<IMarker>>();
for (final Class<? extends IMarker> markerType : hiddenMarkerTypes) {
result.add((Class<IMarker>) markerType);
}
return result;
}
/**
* @see org.eclipse.riena.ui.internal.ridgets.IBasicMarkableRidget#getMarkers()
*/
public Collection<IMarker> getMarkers() {
// if (markers != null) {
// return Collections.unmodifiableSet(markers);
// } else {
// return Collections.emptySet();
// }
return cloneMarkers();
}
/**
* @see org.eclipse.riena.ui.internal.ridgets.IBasicMarkableRidget#getMarkersOfType(java.lang.Class)
*/
public <T extends IMarker> Collection<T> getMarkersOfType(final Class<T> type) {
return Markable.getMarkersOfType(getMarkers(), type);
}
/**
* Hide markers of the given type. Hidden markers of a matching type shall
* be ignored in the UI (i.e. no feedback is shown by the ridget), but are
* not removed from the ridget.
* <p>
* Initially the set of hidden markers is empty. When calling this method
* the {@code type}-argument is added to the set.
* <p>
* <b>Note:</b> the set of marker types that can be successfully ignored is
* implementation detail of concrete implementations of this class.
*
* @param types
* the type of markers to hide. The matching includes sublasses
* @return the set of currently hidden marker types (including type)
*
* @since 3.0
*/
public final Set<Class<IMarker>> hideMarkersOfType(final Class<? extends IMarker>... types) {
if (hiddenMarkerTypes == null) {
hiddenMarkerTypes = new HashSet<Class<? extends IMarker>>();
}
boolean changed = false;
for (final Class<? extends IMarker> clazz : types) {
changed |= hiddenMarkerTypes.add(clazz);
}
if (changed) {
updateMarkers();
fireMarkerHidingPropertyChangeEvent(null, null);
}
return getHiddenMarkerTypes();
}
/**
* @see org.eclipse.riena.ui.internal.ridgets.IBasicMarkableRidget#removeAllMarkers()
*/
public void removeAllMarkers() {
if ((markers != null) && !markers.isEmpty()) {
for (final IMarker marker : markers) {
marker.removeAttributeChangeListener(markerAttributeChangeListener);
}
final Collection<IMarker> oldValue = cloneMarkers();
clearMarkers();
updateMarkers();
final Collection<IMarker> newValue = getMarkers();
fireMarkerPropertyChangeEvent(oldValue, newValue);
fireOutputPropertyChangeEvent(oldValue, newValue);
fireEnabledPropertyChangeEvent(oldValue, newValue);
}
}
/**
* @see org.eclipse.riena.ui.internal.ridgets.IBasicMarkableRidget#removeMarker(org.eclipse.riena.core.marker.IMarker)
* @since 3.0
*/
public boolean removeMarker(final IMarker marker) {
if ((markers != null) && !markers.isEmpty()) {
final Collection<IMarker> oldValue = cloneMarkers();
if (markers.remove(marker)) {
updateMarkers();
final Collection<IMarker> newValue = getMarkers();
fireMarkerPropertyChangeEvent(oldValue, newValue);
fireOutputPropertyChangeEvent(oldValue, newValue);
fireEnabledPropertyChangeEvent(oldValue, newValue);
marker.removeAttributeChangeListener(markerAttributeChangeListener);
return true;
}
}
return false;
}
/**
* TODO javadoc?
*
* @param ridget
* the ridget to set
* @since 3.0
*/
public void setRidget(final IBasicMarkableRidget ridget) {
this.ridget = ridget;
}
/**
* Show markers of the given type. Hidden markers of a matching type shall
* be ignored in the UI (i.e. not feedback is shown by the ridget), but are
* not removed from the ridget.
* <p>
* Initially the set of hidden markers is empty. When calling this method
* the {@code type}-argument is removed from the set.
* <p>
* <b>Note:</b> the set of marker types that can be successfully ignored is
* implementation detail of concrete implementations of this class.
*
* @param types
* the type of markers to show (unhide). The matching includes
* subclasses
* @return the set of currently hidden marker types (including type).
*
* @since 3.0
*/
public final Set<Class<IMarker>> showMarkersOfType(final Class<? extends IMarker>... types) {
if (hiddenMarkerTypes == null) {
return new HashSet<Class<IMarker>>();
}
boolean changed = false;
for (final Class<? extends IMarker> clazz : types) {
changed |= hiddenMarkerTypes.remove(clazz);
}
if (changed) {
updateMarkers();
fireMarkerHidingPropertyChangeEvent(null, null);
}
return getHiddenMarkerTypes();
}
/**
* Callback method triggered when a control is unbound from the
* {@link IRidget}.
* <p>
* The default implementation is empty. Subclasses may override.
*
* @since 3.0
*/
public void unbind() {
// nop
}
// abstract methods
///////////////////
/**
* Updates the UI-control to display the current markers.
*
* @see #getUIControl()
* @see #getMarkers()
*/
public abstract void updateMarkers();
protected Object getUIControl() {
return getRidget().getUIControl();
}
protected IBasicMarkableRidget getRidget() {
return ridget;
}
protected void handleMarkerAttributesChanged() {
propertyChangeSupport.firePropertyChange(new MarkerPropertyChangeEvent(true, getRidget(), getMarkers()));
}
protected boolean hasHiddenMarkers() {
return !getRidget().getMarkersOfType(HiddenMarker.class).isEmpty();
}
// helping methods
//////////////////
private void clearMarkers() {
markers.clear();
}
private Collection<IMarker> cloneMarkers() {
if (markers != null) {
return new ArrayList<IMarker>(markers);
} else {
return Collections.emptySet();
}
}
private void initializeMarkers() {
if (markers == null) {
markers = new ArrayList<IMarker>(1);
}
}
private Boolean isEnabled(final Collection<IMarker> markers) {
boolean result = true;
if (markers != null) {
final Iterator<IMarker> iter = markers.iterator();
while (result && iter.hasNext()) {
result = !(iter.next() instanceof DisabledMarker);
}
}
return Boolean.valueOf(result);
}
private Boolean isOutput(final Collection<IMarker> markers) {
boolean result = false;
if (markers != null) {
final Iterator<IMarker> iter = markers.iterator();
while (!result && iter.hasNext()) {
result = (iter.next() instanceof OutputMarker);
}
}
return Boolean.valueOf(result);
}
private void fireEnabledPropertyChangeEvent(final Collection<IMarker> oldMarkers,
final Collection<IMarker> newMarkers) {
final Boolean oldValue = isEnabled(oldMarkers);
final Boolean newValue = isEnabled(newMarkers);
if (!oldValue.equals(newValue)) {
final PropertyChangeEvent evt = new PropertyChangeEvent(getRidget(), IRidget.PROPERTY_ENABLED, oldValue,
newValue);
propertyChangeSupport.firePropertyChange(evt);
}
}
private void fireMarkerPropertyChangeEvent(final Collection<IMarker> oldMarkers,
final Collection<IMarker> newMarkers) {
propertyChangeSupport.firePropertyChange(new MarkerPropertyChangeEvent(oldMarkers, getRidget(), newMarkers));
}
private void fireMarkerHidingPropertyChangeEvent(final Collection<IMarker> oldMarkers,
final Collection<IMarker> newMarkers) {
propertyChangeSupport.firePropertyChange(IBasicMarkableRidget.PROPERTY_MARKER_HIDING, null, null);
}
private void fireOutputPropertyChangeEvent(final Collection<IMarker> oldMarkers,
final Collection<IMarker> newMarkers) {
final Boolean oldValue = isOutput(oldMarkers);
final Boolean newValue = isOutput(newMarkers);
if (!oldValue.equals(newValue)) {
final PropertyChangeEvent evt = new PropertyChangeEvent(getRidget(), IMarkableRidget.PROPERTY_OUTPUT_ONLY,
oldValue, newValue);
propertyChangeSupport.firePropertyChange(evt);
}
}
// helping classes
//////////////////
private static final class MarkerPropertyChangeEvent extends PropertyChangeEvent implements
IMarkerPropertyChangeEvent {
private static final long serialVersionUID = 1L;
private boolean attributeRelated = false;
private MarkerPropertyChangeEvent(final Object oldValue, final IBasicMarkableRidget source,
final Object newValue) {
super(source, IBasicMarkableRidget.PROPERTY_MARKER, oldValue, newValue);
}
private MarkerPropertyChangeEvent(final boolean attributeRelated, final IBasicMarkableRidget source,
final Object newValue) {
this(null, source, newValue);
this.attributeRelated = attributeRelated;
}
public boolean isAttributeRelated() {
return attributeRelated;
}
}
private final class MarkerAttributeChangeListener implements IMarkerAttributeChangeListener {
public void attributesChanged() {
handleMarkerAttributesChanged();
}
}
}