/******************************************************************************* * Copyright (c) 2011 IBM Corporation 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: IBM Corporation - initial API and * implementation *******************************************************************************/ package org.eclipse.wst.css.ui.internal.text.hover; import org.eclipse.jface.text.AbstractInformationControl; import org.eclipse.jface.text.AbstractReusableInformationControlCreator; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IInformationControl; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.IInformationControlExtension2; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextHover; import org.eclipse.jface.text.ITextHoverExtension; import org.eclipse.jface.text.ITextHoverExtension2; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Region; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Shell; import org.eclipse.wst.css.core.internal.provisional.document.ICSSPrimitiveValue; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.w3c.dom.css.CSSPrimitiveValue; import org.w3c.dom.css.RGBColor; public class CSSColorHover implements ITextHover, ITextHoverExtension, ITextHoverExtension2 { private IInformationControlCreator fInformationControlCreator; public CSSColorHover() { } public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) { final IDocument document = textViewer.getDocument(); if (document instanceof IStructuredDocument) { IStructuredModel model = StructuredModelManager.getModelManager().getModelForRead( (IStructuredDocument) document); try { final IndexedRegion region = model.getIndexedRegion(hoverRegion.getOffset()); if (region instanceof ICSSPrimitiveValue) { return getColorValue((ICSSPrimitiveValue) region); } } finally { if (model != null) model.releaseFromRead(); } } return null; } public IInformationControlCreator getHoverControlCreator() { if (fInformationControlCreator == null) { fInformationControlCreator = new CSSColorInformationControlCreator(); } return fInformationControlCreator; } public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { Object result = getHoverInfo2(textViewer, hoverRegion); return result != null ? result.toString() : null; } /** * Gets an {@link RGB} color value from the primitive value * * @param value the primitive CSS value * @return an RGB value if it can be extracted from the primitive */ private RGB getColorValue(ICSSPrimitiveValue value) { if (value.getParentNode() instanceof RGBColor) { value = (ICSSPrimitiveValue) value.getParentNode(); } RGB rgb = null; switch (value.getPrimitiveType()) { case CSSPrimitiveValue.CSS_RGBCOLOR: rgb = getRGB((RGBColor) value); break; case ICSSPrimitiveValue.CSS_HASH: rgb = getRGBFromHex(value.getStringValue()); break; case CSSPrimitiveValue.CSS_IDENT: rgb = CSSColorNames.getInstance().getRGB(value.getStringValue()); break; } return rgb; } /** * Converts a hex string (either 3-digit or 6-digit notation) into an {@link RGB}. Any invalid hex * color will result in a null RGB value. * * @param hex rgb value in hexadecimal notation (3- or 6-digit) * @return an {@link RGB} based on the hexadecimal value. <code>null</code> if the rgb value is * invalid */ private RGB getRGBFromHex(String hex) { try { final int length = hex.length(); if (length == 4) { // convert 3-digit notation final int r = Integer.parseInt(hex.substring(1, 2), 16); final int g = Integer.parseInt(hex.substring(2, 3), 16); final int b = Integer.parseInt(hex.substring(3, 4), 16); return new RGB((r << 4) | r, (g << 4) | g, (b << 4) | b); } else if (length == 7) { // convert 6-digit notation return new RGB(Integer.parseInt(hex.substring(1, 3), 16), Integer.parseInt( hex.substring(3, 5), 16), Integer.parseInt(hex.substring(5, 7), 16)); } } catch (NumberFormatException e) { } // Invalid hexcode used return null; } /** * Provides an {@link RGB} based on an {@link RGBColor} * * @param color The {@link RGBColor} to extra RGB information from * @return an {@link RGB} based on an {@link RGBColor} */ private RGB getRGB(RGBColor color) { final int red = getColorInt(color.getRed()); final int green = getColorInt(color.getGreen()); final int blue = getColorInt(color.getBlue()); if (red >= 0 && green >= 0 && blue >= 0) return new RGB(red, green, blue); return null; } private int getColorInt(CSSPrimitiveValue value) { if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_PERCENTAGE) { // Percentage of 255 final float percentage = value.getFloatValue(CSSPrimitiveValue.CSS_NUMBER); return capIntValue((int) (percentage / 100 * 255)); } else if (value.getPrimitiveType() == ICSSPrimitiveValue.CSS_INTEGER) { return capIntValue((int) value.getFloatValue(CSSPrimitiveValue.CSS_NUMBER)); } return -1; } /** * Caps the integer value between the ranges of 0 and 255, inclusive. * * @param value integer value to cap * @return a value between 0 and 255, inclusive */ private int capIntValue(int value) { if (value > 255) value = 255; else if (value < 0) value = 0; return value; } public IRegion getHoverRegion(ITextViewer textViewer, int offset) { final IDocument document = textViewer.getDocument(); if (document instanceof IStructuredDocument) { IStructuredModel model = StructuredModelManager.getModelManager().getModelForRead( (IStructuredDocument) document); try { IndexedRegion region = model.getIndexedRegion(offset); if (region instanceof ICSSPrimitiveValue) { // Shift the hover region to the rgb() function instead of the individual numbers if (((ICSSPrimitiveValue) region).getParentNode() instanceof RGBColor) { region = (IndexedRegion) ((ICSSPrimitiveValue) region).getParentNode(); } } if (region != null) { return new Region(region.getStartOffset(), region.getLength()); } } finally { if (model != null) model.releaseFromRead(); } } return null; } private class CSSColorInformationControlCreator extends AbstractReusableInformationControlCreator { protected IInformationControl doCreateInformationControl(Shell parent) { return new CSSColorInformationControl(parent); } } /** * An information control that simply displays color information. The control takes an {@link RGB} * input value and sets its background to the corresponding color. */ private class CSSColorInformationControl extends AbstractInformationControl implements IInformationControlExtension2 { private Color fColor; CSSColorInformationControl(Shell shell) { super(shell, false); create(); } public Point computeSizeConstraints(int widthInChars, int heightInChars) { return new Point(50, 50); } public boolean hasContents() { return fColor != null; } protected void createContent(Composite parent) { } public void setInformation(String information) { } public void dispose() { if (fColor != null) { fColor.dispose(); fColor = null; } super.dispose(); } public void setInput(Object input) { if (input instanceof RGB) { RGB rgb = (RGB) input; if (fColor == null || !rgb.equals(fColor.getRGB())) { if (fColor != null) { // Cleanup any old color fColor.dispose(); } fColor = new Color(getShell().getDisplay(), rgb); setBackgroundColor(fColor); } } } public IInformationControlCreator getInformationPresenterControlCreator() { return new CSSColorInformationControlCreator(); } } }