eu.numberfour.n4js.ui.labeling.helper.N4JSDecoratorRow.java Source code

Java tutorial

Introduction

Here is the source code for eu.numberfour.n4js.ui.labeling.helper.N4JSDecoratorRow.java

Source

/**
 * Copyright (c) 2016 NumberFour AG.
 * 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:
 *   NumberFour AG - Initial API and implementation
 */
package eu.numberfour.n4js.ui.labeling.helper;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import org.eclipse.jface.resource.CompositeImageDescriptor;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.DecorationOverlayIcon;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.xtext.ui.label.AbstractLabelProvider;

import eu.numberfour.n4js.ui.labeling.N4JSLabelProvider;

/**
 * This image descriptor accepts multiple decorators on a single quadrant. In contrast, {@link DecorationOverlayIcon}
 * accepts at most one decorator per quadrant (ie, up to 4 quadrants total).
 * <p>
 * In order to have the outline view running in the background, or just to minimize memory consumption, all image
 * descriptors should be designed with the image registry in mind. For that, an image descriptor should serve as key for
 * the image map managed by {@link AbstractLabelProvider} (of which {@link N4JSLabelProvider} is a subclass).
 * <p>
 * Following the recommendation in {@link ImageDescriptor} an image descriptor shouldn't hold references to images, thus
 * contributing towards both goals of having a background-outline and lower memory-consumption.
 * <p>
 * This class is not thread-reentrant. It is however safe for use outside the Eclipse-UI thread.
 */
public final class N4JSDecoratorRow extends CompositeImageDescriptor {

    private final ImageDescriptor main;
    private final int quadrant;
    private final ImageDescriptor[] decos;

    /**
     * Usage:
     * <ul>
     * <li>Never access directly, use {@link #getMainData()} instead.</li>
     * <li>lazily-initialized, derived data, not considered for equals()/hashCode()</li>
     * </ul>
     */
    private ImageData mainData = null;

    /**
     * Usage:
     * <ul>
     * <li>Never access directly, use {@link #getDecosData()} instead.</li>
     * <li>lazily-initialized, derived data, not considered for equals()/hashCode()</li>
     * </ul>
     */
    private ImageData[] decosData = null;

    @SuppressWarnings("javadoc")
    public N4JSDecoratorRow(ImageDescriptor main, int quadrant, List<ImageDescriptor> decos) {
        Objects.requireNonNull(main);
        if ((quadrant < IDecoration.TOP_LEFT) || (quadrant > IDecoration.BOTTOM_RIGHT)) {
            throw new IllegalArgumentException();
        }
        Objects.requireNonNull(decos);
        this.main = main;
        this.quadrant = quadrant;
        this.decos = decos.toArray(new ImageDescriptor[decos.size()]);
    }

    @SuppressWarnings("javadoc")
    public N4JSDecoratorRow(ImageDescriptor main, int quadrant, ImageDescriptor deco) {
        this(main, quadrant, Collections.singletonList(deco));
    }

    /**
     * Are the decorators aligned vertically on top?
     */
    private boolean isDecoTop() {
        switch (quadrant) {
        case IDecoration.TOP_LEFT:
        case IDecoration.TOP_RIGHT:
            return true;

        default:
            return false;
        }
    }

    /**
     * Do the decorators start on the left border?
     */
    private boolean isDecoLeft() {
        switch (quadrant) {
        case IDecoration.TOP_LEFT:
        case IDecoration.BOTTOM_LEFT:
            return true;

        default:
            return false;
        }
    }

    private int halfDecoHeight() {
        return getDecosHeight() / 2;
    }

    private int halfDecoWidth() {
        return getDecosWidth() / 2;
    }

    /**
     * Distance by which the main image is displaced (from the top border) to partially make room for decorators.
     * <p>
     * In contrast, {@link #marginFromTop()} applies to the decorators.
     */
    private int shiftFromTop() {
        return isDecoTop() ? halfDecoHeight() : 0;
    }

    /**
     * Distance by which the main image is displaced (from the left border) to partially make room for decorators.
     * <p>
     * In contrast, {@link #marginFromLeft()} applies to the decorators.
     */
    private int shiftFromLeft() {
        return isDecoLeft() ? halfDecoWidth() : 0;
    }

    /**
     * This method initializes a field on demand, given that each invocation of the delegate allocates anew.
     */
    private ImageData getMainData() {
        if (null == mainData) {
            mainData = main.getImageData();
        }
        return mainData;
    }

    /**
     * This method initializes a field on demand, given that each invocation of the delegate allocates anew.
     */
    private ImageData[] getDecosData() {
        if (null == decosData) {
            decosData = new ImageData[decos.length];
            for (int i = 0; i < decosData.length; i++) {
                decosData[i] = decos[i].getImageData();
            }
        }
        return decosData;
    }

    /**
     * @return Summation over the width of each decorator.
     */
    private int getDecosWidth() {
        int result = 0;
        for (ImageData dd : getDecosData()) {
            result += dd.width;
        }
        return result;
    }

    /**
     * @return The maximum height over all decorators.
     */
    private int getDecosHeight() {
        int result = 0;
        for (ImageData dd : getDecosData()) {
            result = Math.max(result, dd.height);
        }
        return result;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Arrays.hashCode(decos);
        result = prime * result + ((main == null) ? 0 : main.hashCode());
        result = prime * result + quadrant;
        return result;
    }

    /**
     * This override and that for {@link #hashCode} are essential so that the image registry (accessed from
     * {@code AbstractLabelProvider}) may recognize two different instances of this class as describing the same image.
     * Otherwise a new image is allocated each time.
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof N4JSDecoratorRow)) {
            return false;
        }
        N4JSDecoratorRow other = (N4JSDecoratorRow) obj;
        if (!Arrays.equals(decos, other.decos)) {
            return false;
        }
        if (!main.equals(other.main)) {
            return false;
        }
        if (quadrant != other.quadrant) {
            return false;
        }
        return true;
    }

    /**
     * Bounding box for the image that would result after shifting the main image to make room for decorators, ie
     * including both top and left margins if any.
     * <p>
     * Such image only materializes as an intermediate step during {@link #drawCompositeImage(int, int)}
     */
    private Rectangle getSizeOfShiftedMain() {
        int widthAfterShift = shiftFromLeft() + getMainData().width;
        int heightAfterShift = shiftFromTop() + getMainData().height;
        return new Rectangle(0, 0, widthAfterShift, heightAfterShift);
    }

    /**
     * Bounding box for the image that would result after shifting the image that includes all decorators, accounting
     * for the left-most position (given by {@link #quadrant}) for such decorators-image.
     * <p>
     * The images in question only materialize as intermediate steps during {@link #drawCompositeImage(int, int)}
     */
    private Rectangle getSizeOfShiftedDecos() {
        int widthAfterShift = marginFromLeft() + getDecosWidth();
        int heightAfterShift = marginFromTop() + getDecosHeight();
        return new Rectangle(0, 0, widthAfterShift, heightAfterShift);
    }

    /**
     * The distance from the left border at which the drawing of the decorator row starts, influenced by
     * {@link #quadrant}.
     * <p>
     * In contrast, {@link #shiftFromLeft()} applies to the main image.
     */
    private int marginFromLeft() {
        return isDecoLeft() ? 0 : halfOf(getMainData().width);
    }

    /**
     * The distance from the top border at which the drawing of the decorator row starts, influenced by
     * {@link #quadrant}.
     * <p>
     * In contrast, {@link #shiftFromTop()} applies to the main image.
     */
    private int marginFromTop() {
        return isDecoTop() ? 0 : halfOf(getMainData().height);
    }

    private static int halfOf(int amount) {
        return amount / 2;
    }

    /**
     * A bounding box for the resulting image (ie, after decorators are applied).
     * <p>
     * The result of this method is passed to {@link #drawCompositeImage} as part of allocating image data for the
     * composite as a whole, during an invocation of {@link CompositeImageDescriptor#getImageData()}.
     */
    @Override
    protected Point getSize() {
        Rectangle boundingBox = getSizeOfShiftedMain().union(getSizeOfShiftedDecos());
        return new Point(boundingBox.width, boundingBox.height);
    }

    @Override
    protected void drawCompositeImage(int ignoredWidth, int ignoredHeight) {
        // base image, possibly shifted to accommodate decorators
        drawImage(getMainData(), shiftFromLeft(), shiftFromTop());
        // row of decorators
        int x = marginFromLeft();
        final int y = marginFromTop();
        for (ImageData decoData : getDecosData()) {
            drawImage(decoData, x, y);
            x += decoData.width;
        }
    }

}