com.rcpcompany.uibindings.uiAttributes.UIAttributePainter.java Source code

Java tutorial

Introduction

Here is the source code for com.rcpcompany.uibindings.uiAttributes.UIAttributePainter.java

Source

/*******************************************************************************
 * 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.uiAttributes;

import java.util.List;

import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.Util;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.forms.FormColors;
import org.eclipse.ui.plugin.AbstractUIPlugin;

import com.rcpcompany.uibindings.Constants;
import com.rcpcompany.uibindings.IManager;
import com.rcpcompany.uibindings.IUIAttribute;
import com.rcpcompany.uibindings.internal.Activator;

/**
 * A painter for {@link IUIAttribute}.
 * <p>
 * Used to paint a single virtual {@link IUIAttribute} for a label provider, a grid renderer or
 * similar.
 * <p>
 * Kept as close as possible to StyledCellLabelProvider.
 * 
 * @author Tonny Madsen, The RCP Company
 */
public class UIAttributePainter {
    /**
     * Extra cell height used on XP to ensure the cells that the correct space around checkboxes.
     */
    private static final int EXTRA_CELL_HEIGHT = 3;

    /**
     * The margin around the image and text of a cell.
     */
    public static final int MARGIN = 3;

    /**
     * The parent control of this painter.
     * <p>
     * Certain attributes of the control is used...
     */
    private final Control myParentControl;

    /**
     * The minimum height for a cell to accommodate the check box images.
     */
    private static int myMinHeight;

    /**
     * Returns the minimum height for a cell to accommodate the check box images.
     * 
     * @return the minimum height
     */
    public static int getMinHeight() {
        return myMinHeight;
    }

    private static final String CHECKED_KEY = UIAttributePainter.class.getName() + "$CHECKED";
    private static final String UNCHECKED_KEY = UIAttributePainter.class.getName() + "$UNCHECKED";

    /**
     * Constructs and returns a new UI Attribute painter for the specified parent control.
     * 
     * @param parentControl the parent control
     * @param attribute the UI attribute
     */
    public UIAttributePainter(Control parentControl, IUIAttribute attribute) {
        myParentControl = parentControl;
        myAttribute = attribute;

        /*
         * Here - and not static - as the Display may not be setup correctly before now
         */
        if (JFaceResources.getImageRegistry().getDescriptor(CHECKED_KEY) == null) {
            JFaceResources.getImageRegistry().put(UNCHECKED_KEY, makeShot(parentControl, false));
            JFaceResources.getImageRegistry().put(CHECKED_KEY, makeShot(parentControl, true));
        }
        final FormColors colors = IManager.Factory.getManager().getFormToolkit(parentControl).getColors();

        mySelectionBackground = JFaceResources.getColorRegistry()
                .get(Constants.COLOR_DEFINITIONS_SELECTION_FOCUS_BACKGROUND);
        mySelectionNoFocusBackground = JFaceResources.getColorRegistry()
                .get(Constants.COLOR_DEFINITIONS_SELECTION_NO_FOCUS_BACKGROUND);
        mySelectionForeground = myParentControl.getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);

        myFocusBorder = colors.getBorderColor();
    }

    /**
     * The attribute painted by this painter.
     */
    private IUIAttribute myAttribute = null;

    /**
     * The horizontal alignment.
     */
    private int myHorizontalAlignment = SWT.NONE;
    private Color myDefaultBackground = null;
    private boolean myFocus = false;
    private boolean mySelected = false;

    /**
     * Whether to use the internal values for the painter.
     */
    private boolean myInternalValues = false;
    private String myInternalText = null;
    private Image myInternalImage = null;

    /*
     * Cached results from preparePainter(...)
     */
    private Image myPreparedImage = null;
    private Rectangle myPreparedImageBounds = null;
    private TextLayout myPreparedTextLayout = null;
    private Rectangle myPreparedTextBounds = null;
    private int myPreparedTotalX = 0;
    private int myPreparedTextWidthDelta;

    /**
     * Prepared the painter..
     * 
     * @param gc the GC to use
     */
    protected void preparePainter(GC gc) {
        myPreparedImage = null;
        myPreparedImageBounds = null;
        myPreparedTextBounds = null;
        myPreparedTotalX = 0;

        /*
         * Image
         */
        myPreparedImage = getDisplayImage();
        if (myPreparedImage != null) {
            myPreparedImageBounds = myPreparedImage.getBounds();

            // center the image in the given space
            myPreparedTotalX += myPreparedImageBounds.width;
        }
        /*
         * Text
         */

        final String t = getDisplayText();
        if (t != null) {
            if (myPreparedTextLayout == null) {
                myPreparedTextLayout = new TextLayout(myParentControl.getDisplay());
                myPreparedTextLayout
                        .setOrientation(myParentControl.getStyle() & (SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT));
            }

            /*
             * Set to two different values, to make sure the styles are reset...
             */
            myPreparedTextLayout.setText("");
            myPreparedTextLayout.setText(t);

            myPreparedTextLayout.setFont(getAttribute().getFont());

            // text width without any styles
            final int originalTextWidth = myPreparedTextLayout.getBounds().width;
            boolean containsOtherFont = false;

            final List<StyleRange> styleRanges = getAttribute().getStyleRanges();
            if (styleRanges != null && !styleRanges.isEmpty()) { // user filled styled ranges
                for (final StyleRange styleRange : styleRanges) {
                    myPreparedTextLayout.setStyle(styleRange, styleRange.start,
                            styleRange.start + styleRange.length - 1);
                    if (styleRange.font != null) {
                        containsOtherFont = true;
                    }
                }
            }
            if (containsOtherFont) {
                myPreparedTextWidthDelta = myPreparedTextLayout.getBounds().width - originalTextWidth;
            } else {
                myPreparedTextWidthDelta = 0;
            }
            myPreparedTextBounds = myPreparedTextLayout.getBounds();

            if (myPreparedImageBounds != null) {
                myPreparedTotalX += MARGIN;
                myPreparedTextBounds.x += myPreparedImageBounds.width + MARGIN;
            }

            myPreparedTotalX += myPreparedTextBounds.width;
        }
    }

    /**
     * Measures and updates the bounds needed to paint this UI attribute.
     * 
     * @param gc the GC to use
     * @param bounds the current bounds for the attribute
     */
    public void measure(GC gc, Rectangle bounds) {
        preparePainter(gc);
        bounds.width += myPreparedTextWidthDelta;
    }

    /**
     * Returns the width difference for text of this cell when applying fonts to the text.
     * 
     * @param gc the GC to use for the calculation
     * @return the size difference
     */
    public int getTextWidthDelta(GC gc) {
        preparePainter(gc);
        return myPreparedTextWidthDelta;
    }

    /**
     * Paints this UI attribute within the specified bounds.
     * 
     * @param gc the GC to use
     * @param areaBounds the bounds of the area
     */
    public void paint(GC gc, Rectangle areaBounds) {
        paintTextAndImage(gc, areaBounds);
    }

    private void paintTextAndImage(GC gc, Rectangle areaBounds) {
        preparePainter(gc);
        // Remember colors to restore the GC later
        final Color oldForeground = gc.getForeground();
        final Color oldBackground = gc.getBackground();

        /*
         * Colors
         */
        Color foreground = oldForeground;
        final Color f = getAttribute().getForeground();
        if (f != null) {
            foreground = f;
        }
        if (getAttribute().isEnabled() == Boolean.FALSE) {
            foreground = Display.getCurrent().getSystemColor(SWT.COLOR_GRAY);
        }
        // if (!foreground.equals(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK))) {
        // LogUtils.debug(this, "TODO foreground not black, but " + foreground.getRGB());
        // }

        Color background = getDefaultBackground();
        final Color b = getAttribute().getBackground();
        if (b != null) {
            background = b;
        }

        if (isSelected() || hasFocus()) {
            if (myParentControl.isFocusControl()) {
                background = mySelectionBackground;
                foreground = mySelectionForeground;
            } else {
                background = mySelectionNoFocusBackground;
            }
        }

        if (background != null) {
            gc.setBackground(background);
        }

        // LogUtils.debug(this, "\nf=" + gc.getForeground() + " " + foreground +
        // "\nb=" + gc.getBackground() + " "
        // + background);
        gc.fillRectangle(areaBounds);

        /*
         * Border
         */
        if (hasFocus() && myParentControl.isFocusControl()) {
            gc.setForeground(myFocusBorder);
            // gc.drawFocus(areaBounds.x, areaBounds.y, areaBounds.width,
            // areaBounds.height);
            final int oldLineWidth = gc.getLineWidth();
            gc.setLineWidth(2);
            gc.drawRectangle(areaBounds.x + 1, areaBounds.y + 1, areaBounds.width - 2, areaBounds.height - 2);
            gc.setLineWidth(oldLineWidth);
        }

        gc.setForeground(foreground);

        int offsetX = 0;

        switch (getHorizontalAlignment()) {
        case SWT.CENTER:
            offsetX = Math.max(0, (areaBounds.width - myPreparedTotalX) / 2);
            break;
        case SWT.RIGHT:
            offsetX = Math.max(0, areaBounds.width - myPreparedTotalX - MARGIN);
            break;
        case SWT.NONE:
        case SWT.LEFT:
        default:
            offsetX = MARGIN;
            break;
        }

        if (myPreparedImageBounds != null) {
            final int x = myPreparedImageBounds.x + areaBounds.x + offsetX;
            final int y = myPreparedImageBounds.y + areaBounds.y
                    + Math.max(0, (areaBounds.height - myPreparedImageBounds.height) / 2);
            gc.drawImage(myPreparedImage, x, y);
        }
        if (myPreparedTextBounds != null) {
            final int x = myPreparedTextBounds.x + areaBounds.x + offsetX;
            final int y = myPreparedTextBounds.y + areaBounds.y
                    + Math.max(0, (areaBounds.height - myPreparedTextBounds.height) / 2);
            myPreparedTextLayout.draw(gc, x, y);
        }

        gc.setForeground(oldForeground);
        gc.setBackground(oldBackground);
    }

    private final Color mySelectionBackground;
    private final Color mySelectionNoFocusBackground;

    private final Color mySelectionForeground;

    private final Color myFocusBorder;

    /**
     * Returns the text used for the current attribute if any.
     * 
     * @return the display text or null
     * 
     */
    public String getDisplayText() {
        if (myInternalValues)
            return myInternalText;
        final IObservableValue displayValue = getAttribute().getCurrentValue();
        if (displayValue == null)
            return null;

        final Object value = displayValue.getValue();
        if (value == null)
            return null;
        return value.toString();
    }

    /**
     * Returns the image used for the current attribute if any.
     * 
     * @return the display image or null
     * 
     */
    public Image getDisplayImage() {
        if (myInternalValues)
            return myInternalImage;
        return getAttribute().getImage();
    }

    /**
     * Return the current attribute of the painter.
     * 
     * @return the attribute
     */
    public IUIAttribute getAttribute() {
        return myAttribute;
    }

    /**
     * Returns the current horizontal alignment.
     * 
     * @return the alignment
     */
    public int getHorizontalAlignment() {
        return myHorizontalAlignment;
    }

    /**
     * Sets the horizontal alignment of the painter - one or {@link SWT#LEAD}, {@link SWT#CENTER},
     * or {@link SWT#TRAIL}.
     * 
     * @param alignment the new alignment
     */
    public void setHorizontalAlignment(int alignment) {
        myHorizontalAlignment = alignment;
    }

    /**
     * Sets the default background of the painter area.
     * 
     * @param defaultBackground the background or <code>null</code>
     */
    public void setDefaultBackground(Color defaultBackground) {
        myDefaultBackground = defaultBackground;
    }

    /**
     * Returns the current default background color.
     * 
     * @return the color or <code>null</code>
     */
    public Color getDefaultBackground() {
        return myDefaultBackground;
    }

    /**
     * Makes an image of a Check button in selected or un-selected mode.
     * 
     * @param control the parent control
     * @param type selected or not
     * @return the image
     */
    private Image makeShot(Control control, boolean type) {
        /*
         * First try to load the image directly from the plugin...
         */
        final String osname = System.getProperty("os.name"); //$NON-NLS-1$
        final String osversion = System.getProperty("os.version"); //$NON-NLS-1$

        String imageName = "images/checkbox/" + osname + "-" + osversion;

        /*
         * Check for classic theme
         */
        final RGB widgetBackgroundColor = control.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND).getRGB();
        if (widgetBackgroundColor.red == 212 && widgetBackgroundColor.green == 208
                && widgetBackgroundColor.blue == 200) {
            imageName += "-classic";
        }

        imageName += "-" + type + ".png";
        final ImageDescriptor id = AbstractUIPlugin.imageDescriptorFromPlugin(Activator.ID, imageName);
        if (id != null) {
            final Image i = id.createImage();
            final Rectangle bounds = i.getBounds();
            if (bounds.height + EXTRA_CELL_HEIGHT > myMinHeight) {
                myMinHeight = bounds.height + EXTRA_CELL_HEIGHT;
            }
            return i;
        }

        /*
         * Hopefully no platform uses exactly this color because we'll make it transparent in the
         * image.
         */
        final Color greenScreen = new Color(control.getDisplay(), 222, 223, 224);

        final Shell shell = new Shell(control.getShell(), SWT.NO_TRIM);

        // otherwise we have a default gray color
        shell.setBackground(greenScreen);

        if (Util.isMac()) {
            final Button button2 = new Button(shell, SWT.CHECK);
            final Point bsize = button2.computeSize(SWT.DEFAULT, SWT.DEFAULT);

            // otherwise an image is stretched by width
            bsize.x = Math.max(bsize.x - 1, bsize.y - 1);
            bsize.y = Math.max(bsize.x - 1, bsize.y - 1);
            button2.setSize(bsize);
            button2.setLocation(100, 100);
        }

        final Button button = new Button(shell, SWT.CHECK);
        button.setBackground(greenScreen);
        button.setSelection(type);

        // otherwise an image is located in a corner
        button.setLocation(1, 1);
        final Point bsize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT);

        // otherwise an image is stretched by width
        bsize.x = Math.max(bsize.x - 1, bsize.y - 1);
        bsize.y = Math.max(bsize.x - 1, bsize.y - 1);
        button.setSize(bsize);

        shell.setSize(bsize);

        final Image image;
        GC gc = null;
        try {
            shell.open();
            gc = new GC(shell);

            image = new Image(control.getDisplay(), bsize.x, bsize.y);
            gc.copyArea(image, 0, 0);
        } finally {
            if (gc != null) {
                gc.dispose();
            }
            shell.close();
        }

        final ImageData imageData = image.getImageData();
        imageData.transparentPixel = imageData.palette.getPixel(greenScreen.getRGB());

        final Image img = new Image(control.getDisplay(), imageData);
        image.dispose();

        if (bsize.y + EXTRA_CELL_HEIGHT > myMinHeight) {
            myMinHeight = bsize.y + EXTRA_CELL_HEIGHT;
        }

        // final ImageLoader imageLoader = new ImageLoader();
        // imageLoader.data = new ImageData[] { imageData };
        //
        // imageLoader.save("/tmp/" + imageName, SWT.IMAGE_PNG);

        return img;
    }

    /**
     * Sets the painter to paint a image only based on the checked state.
     * <p>
     * Three states based on value:
     * <dl>
     * <dt><code>true</code></dt>
     * <dd>The painter will show a check in checked state</dd>
     * <dt><code>false</code></dt>
     * <dd>The painter will show a check in unchecked state</dd>
     * <dt><code>null</code></dt>
     * <dd>The painter will <em>not</em> show a check</dd>
     * </dl>
     * 
     * @param checked the wanted state
     */
    public void setCheckbox(Boolean checked) {
        if (checked == null) {
            setInternalValues(null, null);
            return;
        }
        if (checked) {
            setInternalValues(JFaceResources.getImageRegistry().get(CHECKED_KEY), null);
        } else {
            setInternalValues(JFaceResources.getImageRegistry().get(UNCHECKED_KEY), null);
        }
    }

    public void setInternalValues(Image image, String text) {
        myInternalImage = image;
        myInternalText = text;
        myInternalValues = (image != null) || (text != null);
    }

    /**
     * Sets whether the painter has focus or not.
     * 
     * @param hasFocus if it has focus
     */
    public void setFocus(boolean hasFocus) {
        myFocus = hasFocus;
    }

    /**
     * Whether the painter has focus or not.
     * 
     * @return <code>true</code> if the painter has focus
     */
    public boolean hasFocus() {
        return myFocus;
    }

    /**
     * Sets whether the painter is selected or not.
     * 
     * @param isSelected the new selected state
     */
    public void setSelected(boolean isSelected) {
        mySelected = isSelected;
    }

    /**
     * Whether the painter is selected.
     * 
     * @return <code>true</code> if selected
     */
    public boolean isSelected() {
        return mySelected;
    }

    /**
     * Returns the size of the area for this cell.
     * 
     * @param gc the GC used for ther cell
     * @return the size of the needed area
     */
    public Point getSize(GC gc) {
        preparePainter(gc);
        final Rectangle a = new Rectangle(0, 0, 0, 0);
        if (myPreparedImageBounds != null) {
            a.add(myPreparedImageBounds);
        }
        if (myPreparedTextBounds != null) {
            a.add(myPreparedTextBounds);
        }
        /*
         * Make sure the minimum eight is the same as the
         * 
         * Was: For Windows XP, we must add a little to the size as cells otherwise gets too small.
         */
        if (a.height < getMinHeight()) {
            a.height = getMinHeight();
        }
        return new Point(a.width, a.height);
    }
}