/*******************************************************************************
* Copyright (c) 2006-2013 The RCP Company 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:
* The RCP Company - initial API and implementation
*******************************************************************************/
package com.rcpcompany.uibindings.internal.utils;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import com.rcpcompany.uibindings.BindingState;
import com.rcpcompany.uibindings.Constants;
import com.rcpcompany.uibindings.IBinding;
import com.rcpcompany.uibindings.IBindingContext;
import com.rcpcompany.uibindings.IManager;
import com.rcpcompany.uibindings.IUIBindingDecoratorExtenderContext;
import com.rcpcompany.uibindings.IUIBindingsPackage;
import com.rcpcompany.uibindings.IValueBinding;
import com.rcpcompany.uibindings.decorators.extenders.AbstractUIBindingDecoratorExtender;
import com.rcpcompany.uibindings.internal.Activator;
import com.rcpcompany.uibindings.utils.IBindingHighlightContext;
import com.rcpcompany.utils.logging.LogUtils;
/**
* Implementation of {@link IBindingHighlightContext}.
* <p>
* Includes a manager, that keeps track of all the current contexts, and a binding extender that
* acts on the information and colors the relevant bindings.
*
* @author Tonny Madsen, The RCP Company
*/
public class BindingHighlightContext implements IBindingHighlightContext {
/**
* The list of all current contexts.
*/
private static List<BindingHighlightContext> theContexts = new ArrayList<BindingHighlightContext>();
/**
* Constructs and returns a new highlight context.
*/
public BindingHighlightContext() {
theContexts.add(this);
}
@Override
public void dispose() {
setStage(STAGE.DISPOSED);
myBindings.clear();
/*
* Update all bindings
*/
IManager.Factory.getManager().updateBindings(null);
theContexts.remove(this);
}
/**
* The default color to use.
*/
private final Color myDefaultBackgroundColor = JFaceResources.getColorRegistry().get(
Constants.COLOR_DEFINITIONS_DEFAULT_HIGHLIGHT_BACKGROUND);
/**
* The default color to use.
*/
private final Color myBackgroundColor = Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
/**
* The bindings of this context, when it is active...
*/
private final Set<IValueBinding> myBindings = new HashSet<IValueBinding>();
private final Adapter myAdapter = new AdapterImpl() {
@Override
public void notifyChanged(Notification msg) {
if (msg.isTouch()) return;
if (msg.getFeature() == IUIBindingsPackage.Literals.BINDING__STATE) {
final IValueBinding vb = (IValueBinding) msg.getNotifier();
if (vb.getState() == BindingState.DISPOSED) {
myBindings.remove(vb);
}
}
};
};
@Override
public void activate() {
if (myStage != STAGE.INIT) throw new IllegalStateException("Not in initialization state");
if (myEffect == null) {
setEffect(myDefaultBackgroundColor); // TODO preference
}
/*
* Calculate the bindings of the context
*/
for (final IBindingContext bc : IManager.Factory.getManager().getContexts()) {
BINDING: for (final IBinding b : bc.getBindings()) {
if (!(b instanceof IValueBinding)) {
continue;
}
final IValueBinding vb = (IValueBinding) b;
for (final IBindingSelector d : myDetails) {
if (d.isAffected(vb)) {
myBindings.add(vb);
vb.eAdapters().add(myAdapter);
continue BINDING;
}
}
}
}
myDetails.clear();
setStage(STAGE.FADE_IN);
}
@Override
public void deactivate() {
switch (myStage) {
case DISPOSED:
break;
case INIT:
case FADE_OUT:
dispose();
break;
case FADE_IN:
case ACTIVE:
setStage(STAGE.FADE_OUT);
}
}
/**
* Sets the stage of the context.
*
* @param newStage the new stage
*/
protected void setStage(STAGE newStage) {
myStage = newStage;
myStageStartTime = System.currentTimeMillis();
switch (myStage) {
case INIT:
case DISPOSED:
myStageLength = 0;
break;
case FADE_IN:
myStageLength = myFadeInTime;
break;
case ACTIVE:
myStageLength = (myDeactivationPolicy == DEACTIVATION_POLICY.TIMED) ? myDeactivationTime : 0;
break;
case FADE_OUT:
myStageLength = myFadeOutTime;
break;
}
update();
}
/**
* The current stage of this context.
*/
private STAGE myStage = STAGE.INIT;
@Override
public STAGE getStage() {
return myStage;
}
/**
* The start time for the current stage. Only relevant for {@link STAGE#FADE_IN},
* {@link STAGE#ACTIVE} and {@link STAGE#FADE_OUT}.
*/
private long myStageStartTime;
/**
* The length of the current stage. Only relevant for {@link STAGE#FADE_IN},
* {@link STAGE#ACTIVE} and {@link STAGE#FADE_OUT}.
*/
private long myStageLength;
@Override
public void setDeactivatePolicy(DEACTIVATION_POLICY policy) {
if (myStage != STAGE.INIT) throw new IllegalStateException("Not in initialization state");
myDeactivationPolicy = policy;
}
private final Runnable myUpdateRunnable = new Runnable() {
@Override
public void run() {
update();
}
};
/**
* Updates this context.
*/
public void update() {
/*
* Check if the previous stage is over.
*
* If so, change stage and return...
*/
if (myStageLength != 0 && myStageStartTime + myStageLength <= System.currentTimeMillis()) {
/*
* New stage
*/
switch (myStage) {
case INIT:
case DISPOSED:
break;
case FADE_IN:
setStage(STAGE.ACTIVE);
break;
case ACTIVE:
setStage(STAGE.FADE_OUT);
break;
case FADE_OUT:
dispose();
break;
}
return;
}
for (final IValueBinding vb : myBindings) {
vb.updateBinding();
}
/*
* Set-up timer if needed
*/
final Display display = PlatformUI.getWorkbench().getDisplay();
switch (myStage) {
case INIT:
case DISPOSED:
break;
case FADE_IN:
case FADE_OUT:
display.timerExec(FADE_TICK, myUpdateRunnable);
break;
case ACTIVE:
switch (myDeactivationPolicy) {
case MANUAL:
break;
case FIRST_CHANGE:
final EditingDomain ed = IManager.Factory.getManager().getEditingDomain();
ed.getCommandStack().addCommandStackListener(new CommandStackListener() {
@Override
public void commandStackChanged(EventObject event) {
ed.getCommandStack().removeCommandStackListener(this);
setStage(STAGE.FADE_OUT);
}
});
break;
case TIMED:
display.timerExec(myDeactivationTime, myUpdateRunnable);
break;
case RUNNABLE:
try {
if (myDeactivationRunnable != null) {
myDeactivationRunnable.run();
}
} catch (final Exception ex) {
LogUtils.error(myDeactivationRunnable, ex);
}
setStage(STAGE.FADE_OUT);
return;
}
break;
}
}
/**
* The list of details that are affected by this context.
*/
private final List<IBindingSelector> myDetails = new ArrayList<IBindingSelector>();
private IEffect myEffect = null;
private DEACTIVATION_POLICY myDeactivationPolicy = DEACTIVATION_POLICY.TIMED;
private int myFadeInTime = Activator.getDefault().getPreferenceStore()
.getInt(Constants.PREF_HIGHLIGHT_FADE_IN_TIME);
private int myFadeOutTime = Activator.getDefault().getPreferenceStore()
.getInt(Constants.PREF_HIGHLIGHT_FADE_OUT_TIME);
private int myDeactivationTime = Activator.getDefault().getPreferenceStore()
.getInt(Constants.PREF_HIGHLIGHT_ACTIVE_TIME);
private Runnable myDeactivationRunnable;
/**
* Returns a list of all the highlight contexts that can affect the specified binding.
*
* @param binding the binding to test
* @return a list of the contexts that affects the binding or <code>null</code>
*/
public static List<BindingHighlightContext> findAffectedContexts(IValueBinding binding) {
List<BindingHighlightContext> ctx = null;
for (final BindingHighlightContext c : theContexts) {
if (c.myBindings.contains(binding)) {
if (ctx == null) {
ctx = new ArrayList<BindingHighlightContext>();
}
ctx.add(c);
}
}
return ctx;
}
@Override
public void add(IBindingSelector detail) {
if (myStage != STAGE.INIT) throw new IllegalStateException("Not in initialization state");
myDetails.add(detail);
}
@Override
public void add(final IValueBinding binding) {
add(new IBindingSelector() {
@Override
public boolean isAffected(IValueBinding b) {
return binding == b;
}
});
}
@Override
public void add(final EObject obj, final EStructuralFeature feature) {
add(new IBindingSelector() {
@Override
public boolean isAffected(IValueBinding b) {
return (b.getModelObject() == obj && b.getModelFeature() == feature);
}
});
}
@Override
public void setEffect(IEffect effect) {
if (myStage != STAGE.INIT) throw new IllegalStateException("Not in initialization state");
myEffect = effect;
}
@Override
public void setEffect(final Color background) {
setEffect(new IEffect() {
@Override
public void doEffect(IUIBindingDecoratorExtenderContext context, double fadeFactor) {
Color origBackround = context.getBackground();
if (origBackround == null) {
origBackround = myBackgroundColor;
}
final RGB rgb = new RGB(0, 0, 0);
rgb.red = (int) (origBackround.getRed() * (1.0 - fadeFactor) + background.getRed() * fadeFactor);
rgb.green = (int) (origBackround.getGreen() * (1.0 - fadeFactor) + background.getGreen() * fadeFactor);
rgb.blue = (int) (origBackround.getBlue() * (1.0 - fadeFactor) + background.getBlue() * fadeFactor);
final Color color = Activator.getDefault().getResourceManager().createColor(rgb);
context.setBackgound(color);
}
});
}
/**
* Performs the effect of this highlight context.
*
* @param context the extender context
*/
public void doEffect(IUIBindingDecoratorExtenderContext context) {
double fadeFactor = 1.0;
switch (getStage()) {
case FADE_IN:
if (myStageLength != 0) {
fadeFactor = ((double) (System.currentTimeMillis() - myStageStartTime)) / myStageLength;
}
break;
case ACTIVE:
fadeFactor = 1.0;
break;
case FADE_OUT:
if (myStageLength != 0) {
fadeFactor = 1.0 - ((double) (System.currentTimeMillis() - myStageStartTime)) / myStageLength;
}
}
myEffect.doEffect(context, fadeFactor);
}
@Override
public void setFadeInTime(int ms) {
if (myStage != STAGE.INIT) throw new IllegalStateException("Not in initialization state");
myFadeInTime = ms;
}
@Override
public void setFadeOutTime(int ms) {
if (myStage != STAGE.INIT) throw new IllegalStateException("Not in initialization state");
myFadeOutTime = ms;
}
@Override
public void setDeactivationTime(int ms) {
if (myStage != STAGE.INIT) throw new IllegalStateException("Not in initialization state");
myDeactivationTime = ms;
}
@Override
public void setDeactivationRunnable(Runnable runnnable) {
if (myStage != STAGE.INIT) throw new IllegalStateException("Not in initialization state");
myDeactivationRunnable = runnnable;
}
/**
* Extender used to highlight bindings based on the current contexts.
*/
public static class Extender extends AbstractUIBindingDecoratorExtender {
private List<BindingHighlightContext> myAffectedContexts;
@Override
public boolean isEnabled(IValueBinding binding) {
myAffectedContexts = findAffectedContexts(binding);
return myAffectedContexts != null && !myAffectedContexts.isEmpty();
}
@Override
public void extend(IUIBindingDecoratorExtenderContext context) {
if (myAffectedContexts == null || myAffectedContexts.isEmpty()) return;
for (final BindingHighlightContext c : myAffectedContexts) {
switch (c.getStage()) {
case INIT:
case DISPOSED:
break;
case FADE_IN:
case ACTIVE:
case FADE_OUT:
c.doEffect(context);
break;
}
}
}
}
}