/*
 * Copyright (c) 2008-2011, Piccolo2D project, http://piccolo2d.org
 * Copyright (c) 1998-2008, University of Maryland
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
 * and the following disclaimer in the documentation and/or other materials provided with the
 * distribution.
 *
 * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its
 * contributors may be used to endorse or promote products derived from this software without specific
 * prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package edu.umd.cs.piccolox.event;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PLayer;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PDragSequenceEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolo.util.PBounds;
import edu.umd.cs.piccolo.util.PDimension;
import edu.umd.cs.piccolo.util.PNodeFilter;
import edu.umd.cs.piccolox.handles.PBoundsHandle;

/**
 * <code>PSelectionEventHandler</code> provides standard interaction for
 * selection. Clicking selects the object under the cursor. Shift-clicking
 * allows multiple objects to be selected. Dragging offers marquee selection.
 * Pressing the delete key deletes the selection by default.
 * 
 * @version 1.0
 * @author Ben Bederson
 */
public class PSelectionEventHandler extends PDragSequenceEventHandler {
    /**
     * Notification name that identifies a change in the selection. Used with
     * PNotificationCenter.
     */
    public static final String SELECTION_CHANGED_NOTIFICATION = "SELECTION_CHANGED_NOTIFICATION";

    /** The default dash width when displaying selection rectangle. */
    static final int DASH_WIDTH = 5;

    static final int NUM_STROKES = 10;

    /** The current selection. */
    private HashMap selection = null;
    /** List of nodes whose children can be selected. */
    private List selectableParents = null;

    private PPath marquee = null;
    /** Node that marquee is added to as a child. */
    private PNode marqueeParent = null;

    private Point2D presspt = null;
    private Point2D canvasPressPt = null;
    private float strokeNum = 0;
    private Stroke[] strokes = null;

    /** Used within drag handler temporarily. */
    private HashMap allItems = null;

    /** Used within drag handler temporarily. */
    private ArrayList unselectList = null;
    private HashMap marqueeMap = null;

    /** Node pressed on (or null if none). */
    private PNode pressNode = null;

    /** True if DELETE key should delete selection. */
    private boolean deleteKeyActive = true;

    /** Paint applied when drawing the marquee. */
    private Paint marqueePaint;

    /** How transparent the marquee should be. */
    private float marqueePaintTransparency = 1.0f;

    /**
     * Creates a selection event handler.
     * 
     * @param marqueeParent The node to which the event handler dynamically adds
     *            a marquee (temporarily) to represent the area being selected.
     * @param selectableParent The node whose children will be selected by this
     *            event handler.
     */
    public PSelectionEventHandler(final PNode marqueeParent, final PNode selectableParent) {
        this.marqueeParent = marqueeParent;
        selectableParents = new ArrayList();
        selectableParents.add(selectableParent);
        init();
    }

    /**
     * Creates a selection event handler.
     * 
     * @param marqueeParent The node to which the event handler dynamically adds
     *            a marquee (temporarily) to represent the area being selected.
     * @param selectableParents A list of nodes whose children will be selected
     *            by this event handler.
     */
    public PSelectionEventHandler(final PNode marqueeParent, final List selectableParents) {
        this.marqueeParent = marqueeParent;
        this.selectableParents = selectableParents;
        init();
    }

    /**
     * Initializes the PSelectionEventHandler with a marquee stroke.
     */
    protected void init() {
        final float[] dash = new float[2];
        dash[0] = DASH_WIDTH;
        dash[1] = DASH_WIDTH;

        strokes = new Stroke[NUM_STROKES];
        for (int i = 0; i < NUM_STROKES; i++) {
            strokes[i] = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1, dash, i);
        }

        selection = new HashMap();
        allItems = new HashMap();
        unselectList = new ArrayList();
        marqueeMap = new HashMap();
    }

    /**
     * Marks all items as selected.
     * 
     * @param items collection of items to be selected
     */
    public void select(final Collection items) {
        boolean changes = false;
        final Iterator itemIt = items.iterator();
        while (itemIt.hasNext()) {
            final PNode node = (PNode) itemIt.next();
            changes |= internalSelect(node);
        }
        if (changes) {
            postSelectionChanged();
        }
    }

    /**
     * Marks all keys as selected.
     * 
     * @param items map where keys are to be selected
     */
    public void select(final Map items) {
        select(items.keySet());
    }

    /**
     * Select the passed node if not already selected.
     * 
     * @param node node to be selected
     * @return true if node was not already selected
     */
    private boolean internalSelect(final PNode node) {
        if (isSelected(node)) {
            return false;
        }

        selection.put(node, Boolean.TRUE);
        decorateSelectedNode(node);
        return true;
    }

    /**
     * Dispatches a selection changed notification to the PNodificationCenter.
     */
    private void postSelectionChanged() {
        PNotificationCenter.defaultCenter().postNotification(SELECTION_CHANGED_NOTIFICATION, this);
    }

    /**
     * Selected the provided node if not already selected.
     * 
     * @param node node to be selected
     */
    public void select(final PNode node) {
        if (internalSelect(node)) {
            postSelectionChanged();
        }
    }

    /**
     * Adds bound handles to the provided node.
     * 
     * @param node node to be decorated
     */
    public void decorateSelectedNode(final PNode node) {
        PBoundsHandle.addBoundsHandlesTo(node);
    }

    /**
     * Removes all nodes provided from the selection.
     * 
     * @param items items to remove form the selection
     */
    public void unselect(final Collection items) {
        boolean changes = false;
        final Iterator itemIt = items.iterator();
        while (itemIt.hasNext()) {
            final PNode node = (PNode) itemIt.next();
            changes |= internalUnselect(node);
        }
        if (changes) {
            postSelectionChanged();
        }
    }

    /**
     * Removes provided selection node if not already selected.
     * 
     * @param node node to remove from selection
     * 
     * @return true on success
     */
    private boolean internalUnselect(final PNode node) {
        if (!isSelected(node)) {
            return false;
        }

        undecorateSelectedNode(node);
        selection.remove(node);
        return true;
    }

    /**
     * Removes node from selection.
     * 
     * @param node node to be removed from selection
     */
    public void unselect(final PNode node) {
        if (internalUnselect(node)) {
            postSelectionChanged();
        }
    }

    /**
     * Removes bounds handles from node.
     * 
     * @param node to have handles removed from
     */
    public void undecorateSelectedNode(final PNode node) {
        PBoundsHandle.removeBoundsHandlesFrom(node);
    }

    /**
     * Empties the selection.
     */
    public void unselectAll() {
        // Because unselect() removes from selection, we need to
        // take a copy of it first so it isn't changed while we're iterating
        final ArrayList sel = new ArrayList(selection.keySet());
        unselect(sel);
    }

    /**
     * Returns true is provided node is selected.
     * 
     * @param node - node to be tested
     * @return true if succeeded
     */
    public boolean isSelected(final PNode node) {
        return (node != null && selection.containsKey(node));
    }

    /**
     * Returns a copy of the currently selected nodes.
     * 
     * @return copy of selection
     */
    public Collection getSelection() {
        return new ArrayList(selection.keySet());
    }

    /**
     * Gets a reference to the currently selected nodes. You should not modify
     * or store this collection.
     * 
     * @return direct reference to selection
     */
    public Collection getSelectionReference() {
        return Collections.unmodifiableCollection(selection.keySet());
    }

    /**
     * Determine if the specified node can be selected (i.e., if it is a child
     * of the one the list of nodes that can be selected).
     * 
     * @param node node being tested
     * @return true if node can be selected
     */
    protected boolean isSelectable(final PNode node) {
        boolean selectable = false;

        final Iterator parentsIt = selectableParents.iterator();
        while (parentsIt.hasNext()) {
            final PNode parent = (PNode) parentsIt.next();
            if (parent.getChildrenReference().contains(node)) {
                selectable = true;
                break;
            }
            else if (parent instanceof PCamera) {
                for (int i = 0; i < ((PCamera) parent).getLayerCount(); i++) {
                    final PLayer layer = ((PCamera) parent).getLayer(i);
                    if (layer.getChildrenReference().contains(node)) {
                        selectable = true;
                        break;
                    }
                }
            }
        }

        return selectable;
    }

    /**
     * Flags the node provided as a selectable parent. This makes it possible to
     * select its children.
     * 
     * @param node to flag as selectable
     */
    public void addSelectableParent(final PNode node) {
        selectableParents.add(node);
    }

    /**
     * Removes the node provided from the set of selectable parents. This makes
     * its impossible to select its children.
     * 
     * @param node to remove from selectable parents
     */
    public void removeSelectableParent(final PNode node) {
        selectableParents.remove(node);
    }

    /**
     * Sets the node provided as the *only* selectable parent.
     * 
     * @param node node to become the 1 and only selectable parent
     */
    public void setSelectableParent(final PNode node) {
        selectableParents.clear();
        selectableParents.add(node);
    }

    /**
     * Sets the collection of selectable parents as the only parents that are
     * selectable.
     * 
     * @param c nodes to become selectable parents.
     */
    public void setSelectableParents(final Collection c) {
        selectableParents.clear();
        selectableParents.addAll(c);
    }

    /**
     * Returns all selectable parents.
     * 
     * @return selectable parents
     */
    public Collection getSelectableParents() {
        return new ArrayList(selectableParents);
    }

    // //////////////////////////////////////////////////////
    // The overridden methods from PDragSequenceEventHandler
    // //////////////////////////////////////////////////////

    /**
     * Overrides method in PDragSequenceEventHandler so that, selections have
     * marquees.
     * 
     * @param e the event that started the drag
     */
    protected void startDrag(final PInputEvent e) {
        super.startDrag(e);

        initializeSelection(e);

        if (isMarqueeSelection(e)) {
            initializeMarquee(e);

            if (!isOptionSelection(e)) {
                startMarqueeSelection(e);
            }
            else {
                startOptionMarqueeSelection(e);
            }
        }
        else {
            if (!isOptionSelection(e)) {
                startStandardSelection(e);
            }
            else {
                startStandardOptionSelection(e);
            }
        }
    }

    /**
     * Updates the marquee to the new bounds caused by the drag.
     * 
     * @param event drag event
     */
    protected void drag(final PInputEvent event) {
        super.drag(event);

        if (isMarqueeSelection(event)) {
            updateMarquee(event);

            if (!isOptionSelection(event)) {
                computeMarqueeSelection(event);
            }
            else {
                computeOptionMarqueeSelection(event);
            }
        }
        else {
            dragStandardSelection(event);
        }
    }

    /**
     * Ends the selection marquee when the drag is ended.
     * 
     * @param event the event responsible for ending the drag
     */
    protected void endDrag(final PInputEvent event) {
        super.endDrag(event);

        if (isMarqueeSelection(event)) {
            endMarqueeSelection(event);
        }
        else {
            endStandardSelection(event);
        }
    }

    // //////////////////////////
    // Additional methods
    // //////////////////////////

    /**
     * Used to test whether the event is one that changes the selection.
     * 
     * @param pie The event under test
     * @return true if event changes the selection
     */
    public boolean isOptionSelection(final PInputEvent pie) {
        return pie.isShiftDown();
    }

    /**
     * Tests the input event to see if it is selecting a new node.
     * 
     * @param pie event under test
     * @return true if there is no current selection
     */
    protected boolean isMarqueeSelection(final PInputEvent pie) {
        return pressNode == null;
    }

    /**
     * Starts a selection based on the provided event.
     * 
     * @param pie event used to populate the selection
     */
    protected void initializeSelection(final PInputEvent pie) {
        canvasPressPt = pie.getCanvasPosition();
        presspt = pie.getPosition();
        pressNode = pie.getPath().getPickedNode();
        if (pressNode instanceof PCamera) {
            pressNode = null;
        }
    }

    /**
     * Creates an empty marquee child for use in displaying the marquee around
     * the selection.
     * 
     * @param event event responsible for the initialization
     */
    protected void initializeMarquee(final PInputEvent event) {
        marquee = PPath.createRectangle((float) presspt.getX(), (float) presspt.getY(), 0, 0);
        marquee.setPaint(marqueePaint);
        marquee.setTransparency(marqueePaintTransparency);
        marquee.setStrokePaint(Color.black);
        marquee.setStroke(strokes[0]);
        marqueeParent.addChild(marquee);

        marqueeMap.clear();
    }

    /**
     * Invoked when the marquee is being used to extend the selection.
     * 
     * @param event event causing the option selection
     */
    protected void startOptionMarqueeSelection(final PInputEvent event) {
    }

    /**
     * Invoked at the start of the selection. Removes any selections.
     * 
     * @param event event causing a new marquee selection
     */
    protected void startMarqueeSelection(final PInputEvent event) {
        unselectAll();
    }

    /**
     * If the pressed node is not selected unselect all nodes and select the
     * pressed node if it allows it.
     * 
     * @param pie event that started the selection
     */
    protected void startStandardSelection(final PInputEvent pie) {
        // Option indicator not down - clear selection, and start fresh
        if (isSelected(pressNode)) {
            return;
        }

        unselectAll();

        if (isSelectable(pressNode)) {
            select(pressNode);
        }
    }

    /**
     * Toggle the current selection on the node that was just pressed, but leave
     * the rest of the selected nodes unchanged.
     * 
     * @param pie event responsible for the change in selection
     */
    protected void startStandardOptionSelection(final PInputEvent pie) {
        if (isSelectable(pressNode)) {
            if (isSelected(pressNode)) {
                unselect(pressNode);
            }
            else {
                select(pressNode);
            }
        }
    }

    /**
     * Updates the marquee rectangle as the result of a drag.
     * 
     * @param pie event responsible for the change in the marquee
     */
    protected void updateMarquee(final PInputEvent pie) {
        final PBounds b = new PBounds();

        if (marqueeParent instanceof PCamera) {
            b.add(canvasPressPt);
            b.add(pie.getCanvasPosition());
        }
        else {
            b.add(presspt);
            b.add(pie.getPosition());
        }

        marquee.globalToLocal(b);
        marquee.setPathToRectangle((float) b.x, (float) b.y, (float) b.width, (float) b.height);
        b.reset();
        b.add(presspt);
        b.add(pie.getPosition());

        allItems.clear();
        final PNodeFilter filter = createNodeFilter(b);
        final Iterator parentsIt = selectableParents.iterator();
        while (parentsIt.hasNext()) {
            final PNode parent = (PNode) parentsIt.next();

            Collection items;
            if (parent instanceof PCamera) {
                items = new ArrayList();
                for (int i = 0; i < ((PCamera) parent).getLayerCount(); i++) {
                    ((PCamera) parent).getLayer(i).getAllNodes(filter, items);
                }
            }
            else {
                items = parent.getAllNodes(filter, null);
            }

            final Iterator itemsIt = items.iterator();
            while (itemsIt.hasNext()) {
                allItems.put(itemsIt.next(), Boolean.TRUE);
            }
        }
    }

    /**
     * Sets the selection to be all nodes under the marquee.
     * 
     * @param pie event responsible for the new selection
     */
    protected void computeMarqueeSelection(final PInputEvent pie) {
        unselectList.clear();
        // Make just the items in the list selected
        // Do this efficiently by first unselecting things not in the list
        Iterator selectionEn = selection.keySet().iterator();
        while (selectionEn.hasNext()) {
            final PNode node = (PNode) selectionEn.next();
            if (!allItems.containsKey(node)) {
                unselectList.add(node);
            }
        }
        unselect(unselectList);

        // Then select the rest
        selectionEn = allItems.keySet().iterator();
        while (selectionEn.hasNext()) {
            final PNode node = (PNode) selectionEn.next();
            if (!selection.containsKey(node) && !marqueeMap.containsKey(node) && isSelectable(node)) {
                marqueeMap.put(node, Boolean.TRUE);
            }
            else if (!isSelectable(node)) {
                selectionEn.remove();
            }
        }

        select(allItems);
    }

    /**
     * Extends the selection to include all nodes under the marquee.
     * 
     * @param pie event responsible for the change in selection
     */
    protected void computeOptionMarqueeSelection(final PInputEvent pie) {
        unselectList.clear();
        Iterator selectionEn = selection.keySet().iterator();
        while (selectionEn.hasNext()) {
            final PNode node = (PNode) selectionEn.next();
            if (!allItems.containsKey(node) && marqueeMap.containsKey(node)) {
                marqueeMap.remove(node);
                unselectList.add(node);
            }
        }
        unselect(unselectList);

        // Then select the rest
        selectionEn = allItems.keySet().iterator();
        while (selectionEn.hasNext()) {
            final PNode node = (PNode) selectionEn.next();
            if (!selection.containsKey(node) && !marqueeMap.containsKey(node) && isSelectable(node)) {
                marqueeMap.put(node, Boolean.TRUE);
            }
            else if (!isSelectable(node)) {
                selectionEn.remove();
            }
        }

        select(allItems);
    }

    /**
     * Creates a node filter that will filter all nodes not touching the bounds
     * provided.
     * 
     * @param bounds will be used to filter matches
     * 
     * @return newly created filter
     */
    protected PNodeFilter createNodeFilter(final PBounds bounds) {
        return new BoundsFilter(bounds);
    }

    /**
     * Returns the bounds of the current selection marquee.
     * 
     * @return bounds of current selection marquee
     */
    protected PBounds getMarqueeBounds() {
        if (marquee != null) {
            return marquee.getBounds();
        }
        return new PBounds();
    }

    /**
     * Drag selected nodes.
     * 
     * @param e event responsible for the drag
     */
    protected void dragStandardSelection(final PInputEvent e) {
        // There was a press node, so drag selection
        final PDimension d = e.getCanvasDelta();
        e.getTopCamera().localToView(d);

        final PDimension gDist = new PDimension();
        final Iterator selectionEn = getSelection().iterator();
        while (selectionEn.hasNext()) {
            final PNode node = (PNode) selectionEn.next();

            gDist.setSize(d);
            node.getParent().globalToLocal(gDist);
            node.offset(gDist.getWidth(), gDist.getHeight());
        }
    }

    /**
     * Removes marquee and clears selection.
     * 
     * @param e event responsible for the end of the selection
     */
    protected void endMarqueeSelection(final PInputEvent e) {
        // Remove marquee
        allItems.clear();
        marqueeMap.clear();
        marquee.removeFromParent();
        marquee = null;
    }

    /**
     * Ends the "pressed" state of the previously pressed node (if any).
     * 
     * @param e event responsible for the end in the selection
     */
    protected void endStandardSelection(final PInputEvent e) {
        pressNode = null;
    }

    /**
     * This gets called continuously during the drag, and is used to animate the
     * marquee.
     * 
     * @param aEvent event responsible for this step in the drag sequence
     */
    protected void dragActivityStep(final PInputEvent aEvent) {
        if (marquee != null) {
            final float origStrokeNum = strokeNum;
            strokeNum = (strokeNum + 0.5f) % NUM_STROKES; // Increment by
            // partial steps to
            // slow down animation
            if ((int) strokeNum != (int) origStrokeNum) {
                marquee.setStroke(strokes[(int) strokeNum]);
            }
        }
    }

    /**
     * Delete selection when delete key is pressed (if enabled).
     * 
     * @param e the key press event
     */
    public void keyPressed(final PInputEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_DELETE && deleteKeyActive) {
            final Iterator selectionEn = selection.keySet().iterator();
            while (selectionEn.hasNext()) {
                final PNode node = (PNode) selectionEn.next();
                node.removeFromParent();
            }
            selection.clear();
        }
    }

    /**
     * Returns whether the delete key is a supported action.
     * 
     * @return true if delete is allowed
     */
    public boolean getSupportDeleteKey() {
        return deleteKeyActive;
    }

    /**
     * Returns whether the delete key is a supported action.
     * 
     * @return true if delete is allowed
     */
    public boolean isDeleteKeyActive() {
        return deleteKeyActive;
    }

    /**
     * Specifies if the DELETE key should delete the selection.
     * 
     * @param deleteKeyActive state to set for the delete action true = enabled
     */
    public void setDeleteKeyActive(final boolean deleteKeyActive) {
        this.deleteKeyActive = deleteKeyActive;
    }

    /**
     * Class used to filter nodes that intersect with the marquee's bounds.
     */
    protected class BoundsFilter implements PNodeFilter {
        private final PBounds localBounds = new PBounds();
        private final PBounds bounds;

        /**
         * Constructs a BoundsFilter for the given bounds.
         * 
         * @param bounds bounds to be used when testing nodes for intersection
         */
        protected BoundsFilter(final PBounds bounds) {
            this.bounds = bounds;
        }

        /**
         * Returns true if the node is an acceptable selection.
         * 
         * @param node node being tested
         * @return true if node is an acceptable selection
         */
        public boolean accept(final PNode node) {
            localBounds.setRect(bounds);
            node.globalToLocal(localBounds);

            final boolean boundsIntersects = node.intersects(localBounds);
            final boolean isMarquee = node == marquee;
            return node.getPickable() && boundsIntersects && !isMarquee && !selectableParents.contains(node)
                    && !isCameraLayer(node);
        }

        /**
         * Returns whether this filter should accept all children of a node.
         * 
         * @param node node being tested
         * @return true if selection should accept children children of the node
         */
        public boolean acceptChildrenOf(final PNode node) {
            return selectableParents.contains(node) || isCameraLayer(node);
        }

        /**
         * Tests a node to see if it's a layer that has an attached camera.
         * 
         * @param node node being tested
         * @return true if node is a layer with a camera attached
         */
        public boolean isCameraLayer(final PNode node) {
            if (node instanceof PLayer) {
                for (final Iterator i = selectableParents.iterator(); i.hasNext();) {
                    final PNode parent = (PNode) i.next();
                    if (parent instanceof PCamera && ((PCamera) parent).indexOfLayer((PLayer) node) != -1) {
                        return true;
                    }
                }
            }
            return false;
        }
    }

    /**
     * Indicates the color used to paint the marquee.
     * 
     * @return the paint for interior of the marquee
     */
    public Paint getMarqueePaint() {
        return marqueePaint;
    }

    /**
     * Sets the color used to paint the marquee.
     * 
     * @param paint the paint color
     */
    public void setMarqueePaint(final Paint paint) {
        marqueePaint = paint;
    }

    /**
     * Indicates the transparency level for the interior of the marquee.
     * 
     * @return Returns the marquee paint transparency, zero to one
     */
    public float getMarqueePaintTransparency() {
        return marqueePaintTransparency;
    }

    /**
     * Sets the transparency level for the interior of the marquee.
     * 
     * @param marqueePaintTransparency The marquee paint transparency to set.
     */
    public void setMarqueePaintTransparency(final float marqueePaintTransparency) {
        this.marqueePaintTransparency = marqueePaintTransparency;
    }
}