/******************************************************************************* * Copyright (c) 2012-2014 Obeo. * 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: * Obeo - initial API and implementation *******************************************************************************/ package org.eclipse.emf.compare.rcp.ui.internal.mergeviewer; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; import org.eclipse.emf.compare.Diff; import org.eclipse.emf.compare.rcp.ui.mergeviewer.ICompareColor; import org.eclipse.jface.resource.ColorRegistry; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Display; /** * Default implementation that use a cache to store created Color and that is listening to a preference store * for color configuration. * * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> */ public class CompareColorImpl implements RemovalListener<RGB, Color>, ICompareColor { /** Min component for RGB. */ private static final int MIN_RGB_COMPONENT = 0; /** Max component for RGB. */ private static final int MAX_RGB_COMPONENT = 255; /** Loaded color cache size */ private static final int MAX_CACHE_SIZE = 16; /** Gray color component */ private static final int MED_RGB_COMPONENT = 128; /** Scale factor */ private static final double INTERPOLATION_SCALE_1 = 0.6; /** Scale factor */ private static final double INTERPOLATION_SCALE_2 = 0.97; /** Scale factor to compute the color of border. */ private static final double DARKER_BORDER_SCALE_FACTOR = -0.5; /** Incoming color key in theme */ public static final String INCOMING_CHANGE_COLOR_THEME_KEY = "org.eclipse.emf.compare.incomingChangeColor";//$NON-NLS-1$ /** Conflicting color key in theme */ public static final String CONFLICTING_CHANGE_COLOR_THEME_KEY = "org.eclipse.emf.compare.conflictingChangeColor";//$NON-NLS-1$ /** Outgoing color key in theme */ public static final String OUTGOING_CHANGE_COLOR_THEME_KEY = "org.eclipse.emf.compare.outgoingChangeColor";//$NON-NLS-1$ /** Required difference color key in theme */ public static final String REQUIRED_DIFF_COLOR_THEME_KEY = "org.eclipse.emf.compare.requiredChangeColor";//$NON-NLS-1$ /** Unmergeable difference color key in theme */ public static final String UNMERGEABLE_DIFF_COLOR_THEME_KEY = "org.eclipse.emf.compare.unmergeableChangeColor";//$NON-NLS-1$ private final LoadingCache<RGB, Color> fColors; private final Display fDisplay; private final ColorRegistry fColorRegistry; private final boolean fLeftIsLocal; private RGB incomingSelected; private RGB incoming; private RGB incomingFill; private RGB conflictSelected; private RGB conflict; private RGB conflictFill; private RGB outgoingSelected; private RGB outgoing; private RGB outgoingFill; private RGB requiredColor; private RGB requiredBorderColor; private RGB unmergeableColor; private RGB unmergeableBorderColor; /** * Constructor. With this constructor the colors will disposed at the same as the control. * * @param control * Use for get {@link Display}. The colors will be disposed with the control. * @param leftIsLocal * @param colorRegistry * ColorRegistry where to find all needed color. Those color will be available through the * constants: (UNMERGEABLE_DIFF_COLOR_THEME_KEY, REQUIRED_DIFF_COLOR_THEME_KEY, * RESOLVED_CHANGE_COLOR_THEME_KEY, OUTGOING_CHANGE_COLOR_THEME_KEY, * CONFLICTING_CHANGE_COLOR_THEME_KEY, INCOMING_CHANGE_COLOR_THEME_KEY) */ public CompareColorImpl(Display fDisplay, boolean leftIsLocal, ColorRegistry colorRegistry) { this.fDisplay = fDisplay; this.fLeftIsLocal = leftIsLocal; this.fColors = CacheBuilder.newBuilder().maximumSize(MAX_CACHE_SIZE).removalListener(this) .build(new CacheLoader<RGB, Color>() { @Override public Color load(RGB rgb) throws Exception { return new Color(CompareColorImpl.this.fDisplay, rgb); } }); this.fColorRegistry = colorRegistry; updateColors(); } public final void onRemoval(RemovalNotification<RGB, Color> notification) { Color color = notification.getValue(); if (!color.isDisposed()) { color.dispose(); } } private Color getColor(RGB rgb) { if (rgb == null) { return null; } return fColors.getUnchecked(rgb); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.ICompareColor#getFillColor(org.eclipse.emf.compare.Diff, * boolean, boolean, boolean) */ public Color getFillColor(Diff diff, boolean isThreeWay, boolean isIgnoreAncestor, boolean selected) { return getColor(getFillRGB(diff, isThreeWay, isIgnoreAncestor, selected)); } private RGB getFillRGB(Diff diff, boolean isThreeWay, boolean isIgnoreAncestor, boolean selected) { RGB selectedFill = getBackground(); if (isThreeWay && !isIgnoreAncestor) { boolean requiredConflictForWayOfMerge = false; if (diff.getConflict() == null && !requiredConflictForWayOfMerge) { switch (diff.getSource()) { case RIGHT: if (fLeftIsLocal) { return selected ? selectedFill : incomingFill; } return selected ? selectedFill : outgoingFill; case LEFT: if (fLeftIsLocal) { return selected ? selectedFill : outgoingFill; } return selected ? selectedFill : incomingFill; } } else { return selected ? selectedFill : conflictFill; } return selected ? selectedFill : conflictFill; } return selected ? selectedFill : outgoingFill; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.ICompareColor#getStrokeColor(org.eclipse.emf.compare.Diff, * boolean, boolean, boolean) */ public Color getStrokeColor(Diff diff, boolean isThreeWay, boolean isIgnoreAncestor, boolean selected) { return getColor(getStrokeRGB(diff, isThreeWay, isIgnoreAncestor, selected)); } private RGB getStrokeRGB(Diff diff, boolean isThreeWay, boolean isIgnoreAncestor, boolean selected) { if (isThreeWay && !isIgnoreAncestor) { boolean requiredConflictForWayOfMerge = false; if (diff != null && diff.getConflict() == null && !requiredConflictForWayOfMerge) { switch (diff.getSource()) { case RIGHT: if (fLeftIsLocal) { return selected ? incomingSelected : incoming; } return selected ? outgoingSelected : outgoing; case LEFT: if (fLeftIsLocal) { return selected ? outgoingSelected : outgoing; } return selected ? incomingSelected : incoming; } } else { return selected ? conflictSelected : conflict; } return selected ? conflictSelected : conflict; } return selected ? outgoingSelected : outgoing; } protected final void updateColors() { RGB background = getBackground(); unmergeableColor = fColorRegistry.getRGB(UNMERGEABLE_DIFF_COLOR_THEME_KEY); if (unmergeableColor == null) { unmergeableColor = new RGB(255, 205, 180); } unmergeableBorderColor = interpolate(unmergeableColor, background, DARKER_BORDER_SCALE_FACTOR); requiredColor = fColorRegistry.getRGB(REQUIRED_DIFF_COLOR_THEME_KEY); if (requiredColor == null) { requiredColor = new RGB(215, 255, 200); } requiredBorderColor = interpolate(requiredColor, background, DARKER_BORDER_SCALE_FACTOR); conflictSelected = fColorRegistry.getRGB(CONFLICTING_CHANGE_COLOR_THEME_KEY); if (conflictSelected == null) { conflictSelected = new RGB(MAX_RGB_COMPONENT, 0, 0); // RED } conflict = interpolate(conflictSelected, background, INTERPOLATION_SCALE_1); conflictFill = interpolate(conflictSelected, background, INTERPOLATION_SCALE_2); outgoingSelected = fColorRegistry.getRGB(OUTGOING_CHANGE_COLOR_THEME_KEY); if (outgoingSelected == null) { outgoingSelected = new RGB(0, 0, 0); // BLACK } outgoing = interpolate(outgoingSelected, background, INTERPOLATION_SCALE_1); outgoingFill = interpolate(outgoingSelected, background, INTERPOLATION_SCALE_2); incomingSelected = fColorRegistry.getRGB(INCOMING_CHANGE_COLOR_THEME_KEY); if (incomingSelected == null) { incomingSelected = new RGB(0, 0, MAX_RGB_COMPONENT); // BLUE } incoming = interpolate(incomingSelected, background, INTERPOLATION_SCALE_1); incomingFill = interpolate(incomingSelected, background, INTERPOLATION_SCALE_2); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.ICompareColor#dispose() */ public void dispose() { fColors.invalidateAll(); } /** * Get the background of the current display * * @param fDisplay * @return */ private RGB getBackground() { return fDisplay.getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB(); } /** * Interpolate two colors using a scale factor * * @param fg * Foreground color * @param bg * Background color * @param scale * Scale factor * @return resulting {@link RGB} */ private static RGB interpolate(RGB fg, RGB bg, double scale) { final RGB ret; if (fg != null && bg != null) { int red = (int)((1.0 - scale) * fg.red + scale * bg.red); int green = (int)((1.0 - scale) * fg.green + scale * bg.green); int blue = (int)((1.0 - scale) * fg.blue + scale * bg.blue); ret = new RGB(getValidComponent(red), getValidComponent(green), getValidComponent(blue)); } else if (fg != null) { ret = fg; } else if (bg != null) { ret = bg; } else { ret = new RGB(MED_RGB_COMPONENT, MED_RGB_COMPONENT, MED_RGB_COMPONENT); // a gray } return ret; } /** * Check that the component if valid for RGB object (0 < component < 255) * * @param colorComponent * Input component * @return A valid component */ private static int getValidComponent(int colorComponent) { int validvalue = colorComponent; if (colorComponent > MAX_RGB_COMPONENT) { return MAX_RGB_COMPONENT; } else if (colorComponent < MIN_RGB_COMPONENT) { return MIN_RGB_COMPONENT; } return validvalue; } /** * {@inheritDoc} */ public Color getRequiredFillColor() { return getColor(requiredColor); } /** * {@inheritDoc} */ public Color getUnmergeableFillColor() { return getColor(unmergeableColor); } /** * {@inheritDoc} */ public Color getRequiredStrokeColor() { return getColor(requiredBorderColor); } /** * {@inheritDoc} */ public Color getUnmergeableStrokeColor() { return getColor(unmergeableBorderColor); } }