Package org.openquark.gems.client

Source Code of org.openquark.gems.client.TableTopGemPainter

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* 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.
*     * Neither the name of Business Objects nor 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.
*/


/*
* TableTopGemPainter.java
* Creation date: (03/12/02 11:11:00 AM)
* By: Edward Lam
*/
package org.openquark.gems.client;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.TexturePaint;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import javax.swing.ImageIcon;

import org.openquark.gems.client.DisplayedGem.DisplayedPartOutput;
import org.openquark.gems.client.IntellicutManager.IntellicutMode;
import org.openquark.gems.client.internal.effects.GaussianKernel;
import org.openquark.gems.client.internal.effects.SimpleEffects;
import org.openquark.gems.client.internal.effects.Voronoi;
import org.openquark.util.Pair;


/**
* A GemPainter takes a gem and paints it into the tabletop
* @author Edward Lam
*/
class TableTopGemPainter implements DisplayedGemLocationListener, DisplayedGemSizeListener {
   
    /*
     * There are a number of potential performance improvements to this class:
     *
     * Coalescing of line drawing
     * - calling Graphics.drawLine() many times in succession is much faster than drawLine() calls interspersed with other Graphics draw calls.
     *
     * Calling specialized Graphics draw() methods.
     * - a number of Graphics.drawLine() calls is much faster than unspecialized Graphics2D.draw(shape).
     * - drawOval(), drawRoundRect(), fillXXX(), are faster than the unspecialized draw() and fill() methods.
     * - One drawback is that the specialized draw() methods take int arguments, however in some cases the coordinates we want
     *   to specify cannot be expressed in these terms (eg. x = 1.5, y = 0.3).
     *
     * Caching of image data
     * - more caching could be used.
     * - for instance, the halo image for a rectangle (round or not) could be broken into corner sections and edge sections.
     *   The image could be constructed by adding the corner sections to appropriately scaled edge sections.
     *   Probably the same could be done for triangles, rotating the edge section data, if we could figure out how to do the corners.
     *
     * Creation of new Graphics objects using Graphics.create() (then dispose()).
     * - Can be used instead of saving and resetting Graphics object properties (such as clip or color).
     * - This is heavily optimized, as it is used extensively within AWT/Swing.
     *
     * We might also be able to save some work painting triangles, if we check for intersection using Graphics2D.hit().
     *
     */
   
    /** The tabletop in which to paint. */
    private final TableTop tableTop;

    /** Crazy paving cache **/
    private final transient Map<DisplayedGem, Voronoi> crazyPavingCache = new WeakHashMap<DisplayedGem, Voronoi>();
   
    /** Cached gem tape label */
    private static final transient Map<DisplayedGem, BufferedImage> tapeLabelCache = new WeakHashMap<DisplayedGem, BufferedImage>();
   
    /** Cached gem field name tape label, used for RecordCreationGems */
    private static final transient Map<DisplayedGem, Map<String, BufferedImage>> fieldNameTagLabelCache = new WeakHashMap<DisplayedGem, Map<String, BufferedImage>>();
   
    /** Run halo cache */
    private final transient Map<DisplayedGem, BufferedImage> runHaloCache = new WeakHashMap<DisplayedGem, BufferedImage>();
   
    /** Select halo cache */
    private final transient Map<DisplayedGem, Pair<Color, BufferedImage>> selectHaloCache = new WeakHashMap<DisplayedGem, Pair<Color, BufferedImage>>();

    /** The image to use to indicate that a code gem's code editor is open but not selected. */
    private static final Image codeEditorOpenImage;
   
    /** The image to use to indicate that a code gem's code editor is open and selected. */
    private static final Image codeEditorOpenAndSelectedImage;
   
    /** The crack color for non-photolook tabletops. */
    private static final Color PLAIN_CRACK_COLOUR = new Color(200, 200, 200);
   
    /** The text outline colour for target gem text. */
    private static final Color TARGET_TEXT_OUTLINE_COLOUR = new Color(5, 5, 5, 100);

    // Initialize the open code editor image.
    static {
        codeEditorOpenImage = new ImageIcon(TableTopGemPainter.class.getResource("/Resources/codeEditorOpen.gif")).getImage();
        codeEditorOpenAndSelectedImage = new ImageIcon(TableTopGemPainter.class.getResource("/Resources/selectedCodeEditorOpen.gif")).getImage();
    }

    /**
     * Constructor for a tabletop gem painter.
     */
    TableTopGemPainter(TableTop tableTop) {
        this.tableTop = tableTop;
    }
   
    /**
     * The paint method for a Gem.
     * @param g2d Graphics2D the graphics context
     * @param displayedGem DisplayedGem the gem to paint
     */
    public void paintGem(DisplayedGem displayedGem, Graphics2D g2d) {
       
        Color prevColour = g2d.getColor();

        // Draw the halo image if necessary.
        paintHalo(displayedGem, g2d);
       
        // Check for an intersection with the paint region
        if (displayedGem.getBounds().intersects(g2d.getClipBounds())) {

            // Paint the body.
            paintBody(displayedGem, g2d);
           
            // Paint the brokenness indicators as necessary
            paintCrazyPaving(displayedGem, g2d);

            // Paint the focus outline
            paintFocus(displayedGem, g2d);

            // Draw the output line and bind point if output connected (colour based on output type)
            paintOutputPart(displayedGem, g2d);

            // Draw the input lines and bind points
            int scArity = displayedGem.getNDisplayedArguments();
            for (int i = 0; i < scArity; i++) {
                paintInputPart(displayedGem, i, g2d);
            }
           
            displayedGem.getDisplayedGemShape().getInnerComponentInfo().paint(tableTop, g2d);
        }

        g2d.setColor(prevColour);
    }
   
    /**
     * Paint the focus outline (if any) for the given displayed gem.
     * @param displayedGem
     * @param g2d
     */
    private void paintFocus(DisplayedGem displayedGem, Graphics2D g2d) {

        if (tableTop.getFocusedDisplayedGem() == displayedGem) {

            BasicStroke oldStroke = (BasicStroke)g2d.getStroke();
            BasicStroke highlightStroke;
           
            if (displayedGem.getDisplayedGemShape() instanceof DisplayedGemShape.Triangular && tableTop.getTableTopPanel().isPhotoLook()) {
                highlightStroke = new BasicStroke(oldStroke.getLineWidth() * 4, BasicStroke.CAP_ROUND, oldStroke.getLineJoin(),
                                                  oldStroke.getMiterLimit());
                g2d.setColor(getTableTopBaseColour());
               
            } else {
                highlightStroke = new BasicStroke(oldStroke.getLineWidth(), BasicStroke.CAP_ROUND, oldStroke.getLineJoin(),
                                                  oldStroke.getMiterLimit(), new float[]{1, 12}, 0);
                g2d.setColor(Color.yellow);
            }
           
            g2d.setStroke(highlightStroke);
            g2d.draw(displayedGem.getBodyShape());
           
            // Restore the original Stroke object
            g2d.setStroke(oldStroke);
        }
       
    }

    /**
     * Paint the halo for the given displayed gem, as necessary.
     * @param displayedGem the displayed gem in question.
     * @param g2d the graphics context into which to draw.
     */
    private void paintHalo(DisplayedGem displayedGem, Graphics2D g2d) {

        // If this Gem is selected or running, check for an intersection with the halo
        Rectangle haloRect = getHaloBounds(displayedGem);
        if (haloRect.intersects(g2d.getClipBounds())) {

            // Grab the appropriate halo image (if any)
            BufferedImage haloImage = null;
            if (tableTop.isRunning(displayedGem)) {
                haloImage = getRunHaloImage(displayedGem);

            } else if (tableTop.isSelected(displayedGem)){
                haloImage = getSelectHaloImage(displayedGem, getGemHaloColour(displayedGem));
            }

            // If there is a halo image, draw this buffer with the correct offset
            if (haloImage != null) {
                g2d.drawImage(haloImage, null, haloRect.x, haloRect.y);
            }
        }
    }
   
    /**
     * Determine the Gem's halo bounds.
     * This is bounds of the selection halo which appears when the gem is selected.
     * @param displayedGem
     * @return Rectangle the bounds of the body section
     */
    private Rectangle getHaloBounds(DisplayedGem displayedGem){
        Rectangle haloRect = new Rectangle(displayedGem.getDisplayedBodyPart().getBounds());
        int haloPlusBlur = DisplayConstants.HALO_SIZE + DisplayConstants.HALO_BLUR_SIZE;
        haloRect.x -= haloPlusBlur;
        haloRect.y -= haloPlusBlur;
        haloRect.width += haloPlusBlur * 2;
        haloRect.height += haloPlusBlur * 2;
        return haloRect;
    }

    /**
     * Return a Gem's halo, in the given highlight colour.
     * @param displayedGem
     * @param gemHighlightColour Color the colour of the halo
     * @return BufferedImage
     */
    private BufferedImage getHaloImage(DisplayedGem displayedGem, Color gemHighlightColour) {
       
        Rectangle haloRect = getHaloBounds(displayedGem);
       
        // Build blurred shape in buffered image
        BufferedImage haloImage = new BufferedImage(haloRect.width, haloRect.height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D bg = haloImage.createGraphics();
        bg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        bg.setPaint(gemHighlightColour);
       
        Shape blurShape;
       
        // Define and draw shape
        if (displayedGem.getDisplayedGemShape() instanceof DisplayedGemShape.Triangular) {
            // triangular
            Polygon blurPoly = new Polygon();
            int LHSPerpendicular = (int) Math.sqrt(DisplayConstants.HALO_SIZE * DisplayConstants.HALO_SIZE - DisplayConstants.HALO_BEVEL_SIZE * DisplayConstants.HALO_BEVEL_SIZE);
            Point blur1 = new Point(DisplayConstants.HALO_SIZE + DisplayConstants.HALO_BLUR_SIZE - DisplayConstants.HALO_BEVEL_SIZE, DisplayConstants.HALO_SIZE + DisplayConstants.HALO_BLUR_SIZE - LHSPerpendicular);
            Point blur2 = new Point(haloRect.width, haloRect.height / 2);
            Point blur3 = new Point(DisplayConstants.HALO_SIZE + DisplayConstants.HALO_BLUR_SIZE - DisplayConstants.HALO_BEVEL_SIZE, haloRect.height - (DisplayConstants.HALO_SIZE + DisplayConstants.HALO_BLUR_SIZE - LHSPerpendicular));
            blurPoly.addPoint(blur1.x, blur1.y);
            blurPoly.addPoint(blur2.x, blur2.y);
            blurPoly.addPoint(blur3.x, blur3.y);
            blurShape = blurPoly;
           
        } else {
            // rectangular
            double haloX = DisplayConstants.HALO_SIZE + DisplayConstants.HALO_BLUR_SIZE - DisplayConstants.HALO_BEVEL_SIZE;
            double haloY = DisplayConstants.HALO_SIZE + DisplayConstants.HALO_BLUR_SIZE - DisplayConstants.HALO_BEVEL_SIZE;
            double haloWidth = haloRect.width - 2 * haloX;
            double haloHeight = haloRect.height - 2 * haloY;

            if (displayedGem.getDisplayedGemShape() instanceof DisplayedGemShape.Oval) {
                double arcSize = haloRect.getHeight()/2.0;
                blurShape = new RoundRectangle2D.Double(haloX, haloY, haloWidth, haloHeight, arcSize, arcSize);
           
            } else if (displayedGem.getDisplayedGemShape() instanceof DisplayedGemShape.Rectangular) {
                blurShape = new Rectangle2D.Double(haloX, haloY, haloWidth, haloHeight);
               
            } else {
                throw new IllegalArgumentException("Don't know how to paint this gem shape: " + displayedGem.getDisplayedGemShape().getClass());
            }
        }
       
        bg.fill(blurShape);
        bg.dispose();

        // Blur shape
        ConvolveOp blur = new ConvolveOp(new GaussianKernel(DisplayConstants.HALO_BLUR_SIZE));
        haloImage = blur.filter(haloImage, null);

        return haloImage;
    }

    /**
     * Return a Gem's run halo
     * @param displayedGem
     * @return BufferedImage the run halo
     */
    private BufferedImage getRunHaloImage(DisplayedGem displayedGem) {
        BufferedImage cachedRunHaloImage = runHaloCache.get(displayedGem);
        if (cachedRunHaloImage == null) {
            cachedRunHaloImage = getHaloImage(displayedGem, DisplayConstants.RUN_HALO_COLOUR);
            runHaloCache.put(displayedGem, cachedRunHaloImage);
        }
        return cachedRunHaloImage;
    }

    /**
     * Return a Gem's selection halo
     * @param displayedGem
     * @param haloColour
     * @return BufferedImage the selection halo
     */
    private BufferedImage getSelectHaloImage(DisplayedGem displayedGem, Color haloColour) {

        BufferedImage cachedSelectHaloImage;
        Pair<Color, BufferedImage> cachedColourImagePair = selectHaloCache.get(displayedGem);
       
        if (cachedColourImagePair == null || !cachedColourImagePair.fst().equals(haloColour)) {
            cachedSelectHaloImage = getHaloImage(displayedGem, haloColour);
            selectHaloCache.put(displayedGem, new Pair<Color, BufferedImage>(haloColour, cachedSelectHaloImage));

        } else {
            cachedSelectHaloImage = cachedColourImagePair.snd();
        }

        return cachedSelectHaloImage;
    }

    /**
     * Paint broken cracks to show that a gem is broken.
     * @param displayedGem
     * @param g2d
     */
    private void paintCrazyPaving(DisplayedGem displayedGem, Graphics2D g2d) {
        // If this Gem is 'broken' draw the 'crazy paving'
        if (displayedGem.getGem().isBroken()) {
            Voronoi cachedCrazyPaving = crazyPavingCache.get(displayedGem);

            if (cachedCrazyPaving == null) {
                // Generate an appropriate crazy paving effect
                cachedCrazyPaving = Voronoi.makeRandomAreaVoronoi(displayedGem.getDisplayedBodyPart().getBounds(), DisplayConstants.CRACK_POINTS_PER_QTR);
                crazyPavingCache.put(displayedGem, cachedCrazyPaving);
            }  
           
            // Clip to triangle region
            Shape oldClip = g2d.getClip();
            g2d.setClip(displayedGem.getBodyShape());
           
            // Draw the crazy paving!
            g2d.setColor(getCrackColour(displayedGem));
            cachedCrazyPaving.show(g2d, true, false);

            // Restore clipping
            g2d.setClip(oldClip);
        }  
    }
   
    /**
     * Get an InnerComponentInfo representing the painting of a tape label.
     * @param displayedGem the displayed gem into which to paint.
     * @return the corresponding InnerComponentInfo.
     */
    static DisplayedGemShape.InnerComponentInfo getNameTapeLabelInfo(final DisplayedGem displayedGem) {
       
        return new DisplayedGemShape.InnerComponentInfo() {
            Font font = GemCutterPaintHelper.getTitleFont();

            public Rectangle getBounds() {
                Graphics2D g2d = (Graphics2D)DisplayedGemShape.getGraphics();
             
                return paintNameTapeLabel(displayedGem, font, g2d);
            }


            public List<Rectangle> getInputNameLabelBounds(){
                if(! (displayedGem.getGem() instanceof RecordCreationGem)){
                    return null;
                }

                final Graphics2D g2d = (Graphics2D)DisplayedGemShape.getGraphics();
                return paintFieldNameTag(displayedGem, font, g2d);
            }

            public void paint(TableTop tableTop, Graphics2D g2d) {

                Gem gem = displayedGem.getGem();

                // Draw the field names instead if the gem is a recordCreationGem
                if (gem instanceof RecordCreationGem){
                    paintFieldNameTag(displayedGem, font, g2d);                  

                } else {
                    Rectangle labelBounds = paintNameTapeLabel(displayedGem, font, g2d);

                    // If the gem is a code gem and its code editor is open then draw the appropriate indication of this.
                    if (gem instanceof CodeGem && tableTop.isCodeEditorVisible((CodeGem)gem)) {

                        // If one of the editor's children has focus then we want to use the selected open
                        // editor image to indicate this editor is "activated"
                        Image editorImage = null;
                        if (tableTop.getCodeGemEditor((CodeGem)gem).isFocused()) {
                            editorImage = codeEditorOpenAndSelectedImage;
                        } else {
                            editorImage = codeEditorOpenImage;
                        }

                        // Draw the image just below the left side of the name text
                        g2d.drawImage(editorImage, labelBounds.x, labelBounds.y + labelBounds.height - 1, null);
                    }
                }
            }

        };
    }
   
    /**
     * Paint a label with the gem's display text, in the middle of the gem.
     * @param displayedGem
     * @param font
     * @param g2d
     * @return the string bounds of the tape label.
     */
    private static Rectangle paintNameTapeLabel(DisplayedGem displayedGem, Font font, Graphics2D g2d) {
        boolean isLetGem = displayedGem.getGem() instanceof CollectorGem;
       
        // Set the text font
        g2d.setFont(font);
        FontMetrics fm = g2d.getFontMetrics();
       
        // Get the gem text
        String gemText = displayedGem.getDisplayText();
       
        // Get the label (create as necessary).
        BufferedImage labelImage = tapeLabelCache.get(displayedGem);
        if (labelImage == null) {
            // Create label image
            int tearMarginMedian = (DisplayConstants.INPUT_OUTPUT_LABEL_MARGIN - 1) / 2;
            Rectangle fullLabelSize = fm.getStringBounds(gemText, g2d).getBounds();
            labelImage = SimpleEffects.makeRippedTapeStripLabel(fullLabelSize.getSize(), tearMarginMedian, DisplayConstants.LABEL_COLOUR);
            tapeLabelCache.put(displayedGem, labelImage);
        }

        // Clip to triangle region, this _could_ be the inner triangle for the label...
        Shape oldClip = g2d.getClip();
        g2d.clip(displayedGem.getBodyShape());

        // Get the bounds of the body.
        Rectangle bodyBounds = displayedGem.getDisplayedBodyPart().getBounds();

        // Draw the label image.
        int labelX = isLetGem ? bodyBounds.x + (bodyBounds.width - labelImage.getWidth()) / 2 :
                                bodyBounds.x + DisplayConstants.BEVEL_WIDTH_X + 1;
        int labelY = bodyBounds.y + ((bodyBounds.height - labelImage.getHeight()) / 2);
        g2d.drawImage(labelImage, null, labelX, labelY);
       
        // Set the text colour
        g2d.setColor(Color.black);

        // Determine the baseline for vertically centered text
        float baselineToCentre = (float)((fm.getAscent() - fm.getDescent()) / 2.0);
        float centredBaseline = (float)bodyBounds.getCenterY() + baselineToCentre;
       
        // Draw the title
        float labelMargin = isLetGem ? DisplayConstants.LET_LABEL_MARGIN : (DisplayConstants.BEVEL_WIDTH_X + DisplayConstants.INPUT_OUTPUT_LABEL_MARGIN + 1);
        g2d.drawString(gemText, bodyBounds.x + labelMargin, centredBaseline - 1);
       
        // Restore clip
        g2d.setClip(oldClip);
       
        Rectangle bounds = fm.getStringBounds(gemText, g2d).getBounds();
       
        return new Rectangle(labelX, labelY, bounds.width, bounds.height);
    }
   
   
    /**
     * Paint labels for the RecordCreationGem's fields, beside its corresponding input.
     * @param dGem
     * @param font
     * @param g2d
     */
    private static List<Rectangle> paintFieldNameTag(DisplayedGem dGem, Font font, Graphics2D g2d){
        // the rectangle which will be the union of all rectangles
        List<Rectangle> nameTagBounds = new ArrayList<Rectangle>();

        // Set the text font & color
        g2d.setFont(font);
        g2d.setColor(Color.black);
       
        FontMetrics fm = g2d.getFontMetrics();
        // Determine the baseline for vertically centered text
        float baselineToCentre = (float)((fm.getAscent() - fm.getDescent()) / 2.0);
       
        // Get the fieldName to labelImage map for the gem
        Map<String, BufferedImage> textToImageCache = fieldNameTagLabelCache.get(dGem);
        if (textToImageCache == null){
            textToImageCache = new HashMap<String, BufferedImage>();
        }
       
        // used for adjusting the X point for the label
        int inBoundsWidth = dGem.getDisplayedGemShape().getInBounds().width;
       
        // Clip to triangle region, this _could_ be the inner triangle for the label...
        Shape oldClip = g2d.getClip();
        g2d.clip(dGem.getBodyShape());
       
        // Draw the label for each field name
        List<String> names = ((RecordCreationGem)dGem.getGem()).getCopyOfFieldsList();
        for (int i = 0; i < names.size(); i++) {
           
            String fieldNameText = names.get(i);

            BufferedImage labelImage = textToImageCache.get(fieldNameText);
            // Create the label if doesn't already exist in the cache
            if (labelImage == null) {
                int tearMarginMedian = (DisplayConstants.INPUT_OUTPUT_LABEL_MARGIN - 1) / 2;
                Rectangle fullLabelSize = fm.getStringBounds(fieldNameText, g2d).getBounds();
                labelImage = SimpleEffects.makeRippedTapeStripLabel(fullLabelSize.getSize(), tearMarginMedian, DisplayConstants.LABEL_COLOUR);
                textToImageCache.put(fieldNameText, labelImage);
            }

            // Draw the label image.
            Point connectPoint = dGem.getDisplayedGemShape().getInputConnectPoint(i);
            int labelX = connectPoint.x + inBoundsWidth + DisplayConstants.BEVEL_WIDTH_X ;
            int labelY = connectPoint.y - labelImage.getHeight()/2;
            g2d.drawImage(labelImage, null, labelX, labelY);

            // draw the title, location is adjusted so the string is in the center of the label image
            g2d.drawString(fieldNameText, (float)labelX + DisplayConstants.INPUT_OUTPUT_LABEL_MARGIN, (float)connectPoint.y + baselineToCentre );
           
            // The rectangle of the whole label
            Rectangle nameTagBound = new Rectangle(labelX, labelY, labelImage.getWidth(), labelImage.getHeight());
            nameTagBounds.add(nameTagBound);
        }
       
        // Place the map for this gem in the cache
        fieldNameTagLabelCache.put(dGem, textToImageCache);
   
        // Restore clip
        g2d.setClip(oldClip);
       
        return nameTagBounds;
   
    }
   
    /**
     * Get an InnerComponentInfo representing the painting of a name in bold text.
     * @param displayedGem the displayed gem into which to paint.
     * @return the corresponding InnerComponentInfo.
     */
    static DisplayedGemShape.InnerComponentInfo getBoldNameInfo(final DisplayedGem displayedGem) {
        return new DisplayedGemShape.InnerComponentInfo() {

            public Rectangle getBounds() {
                Graphics2D g2d = (Graphics2D)DisplayedGemShape.getGraphics();
                return paintBoldName(displayedGem, Color.BLACK, null, g2d);
            }

            public void paint(TableTop tableTop, Graphics2D g2d) {
                Color textColour = getTextColour(displayedGem);
                Color outlineColour = tableTop.getTargetDisplayedCollector() == displayedGem ? TARGET_TEXT_OUTLINE_COLOUR : null;
                paintBoldName(displayedGem, textColour, outlineColour, g2d);
            }

            public List<Rectangle> getInputNameLabelBounds() {
                return null;
            }
        };
    }
   
    /**
     * Paint the gem's display text in bold, in the middle of the gem.
     * @param displayedGem the gem for which the name will be painted.
     * @param textColour the colour of the text.
     * @param outlineColour the colour of the outline, if non-null.
     * @param g2d
     */
    private static Rectangle paintBoldName(DisplayedGem displayedGem, Color textColour, Color outlineColour, Graphics2D g2d) {
       
        String displayText = displayedGem.getDisplayText();
        Rectangle bodyRect = displayedGem.getDisplayedBodyPart().getBounds();

        // Determine the baseline for vertically centred text
        g2d.setFont(GemCutterPaintHelper.getBoldFont());
        FontMetrics fm = g2d.getFontMetrics();

        float baselineToCentre = (float)((fm.getAscent() - fm.getDescent()) / 2.0);
        float centredBaseline = (float)(bodyRect.getCenterY() + baselineToCentre);
       
        float textX = bodyRect.x + DisplayConstants.LET_LABEL_MARGIN;
        float textY = centredBaseline - 1;      // seems to be off by 1 for some reason.
       
        if (outlineColour != null) {
            g2d.setColor(outlineColour);
            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
           
            float outlineX = textX - DisplayConstants.OUTLINE_SIZE;
            float outlineY = textY - DisplayConstants.OUTLINE_SIZE;
           
            for (int x = 0, xmax = 2 * DisplayConstants.OUTLINE_SIZE; x <= xmax; x++) {
                for (int y = 0, ymax = 2 * DisplayConstants.OUTLINE_SIZE; y <= ymax; y++) {
                    g2d.drawString(displayText, outlineX + x, outlineY + y);                           
                }
            }
           
            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
        }
       
        // Draw the title
        g2d.setColor(textColour);
        g2d.drawString(displayText, textX, textY);
       
        Rectangle bounds = fm.getStringBounds(displayText, g2d).getBounds();
       
        return new Rectangle((int)textX, (int)textY, bounds.width, bounds.height);
    }
   
    /**
     * Get a new BufferedImage with concentric rings alternating between two given colours.
     * Note: due to a bug in GradientPaint, if paint1 is a GradientPaint, and paint2 is an instance of Color, any transparency
     *   in paint1 will show paint2.
     * @param width the width of the image.
     * @param height the height of the image.
     * @param paint1 ring paint.  This will be the paint for the centre ring.
     * @param paint2 ring paint.
     * @return the resulting image.
     */
    private static BufferedImage getTargetImage(int width, int height, Paint paint1, Paint paint2) {
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
        Graphics2D g2d = image.createGraphics();

        // Fill with paint2.
        g2d.setPaint(paint2);
        g2d.fillRect(0, 0, width, height);
       
        int nRings = (((width+1) / 2) / DisplayConstants.TARGET_RING_WIDTH) + 1;
        Point centerPoint = new Point(width/2, height/2);
        g2d.setPaint(paint1);

        // Draw circles with paint1.
        for (int i = nRings - 1; i > -1; i--) {
            if (i % 2 == 0) {
                drawCircle(g2d, (i+1) * DisplayConstants.TARGET_RING_WIDTH, DisplayConstants.TARGET_RING_WIDTH, centerPoint);
            }
        }
       
        return image;
    }
   
    /**
     * Draw a circle into the given graphics object.
     * @param g2d the graphics object into which to draw.
     * @param radius the radius of the circle.
     * @param lineWidth the width of the line used to draw the circle.
     * @param centerPoint the centre of the circle.
     */
    private static void drawCircle(Graphics2D g2d, int radius, int lineWidth, Point centerPoint) {
        int xcenter = centerPoint.x;
        int ycenter = centerPoint.y;
       
        Stroke oldStroke = g2d.getStroke();
        g2d.setStroke(new BasicStroke(lineWidth));
       
        // save rendering hints, turn on anti-aliasing so that round edges look round.
        Object oldAntiAliasRenderingHint = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.drawOval(xcenter-radius, ycenter-radius, 2*radius, 2*radius);//fill circles
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAntiAliasRenderingHint);
       
        g2d.setStroke(oldStroke);
    }

    /**
     * Paint the body of the displayed gem.
     * @param displayedGem
     * @param g2d
     */
    private void paintBody(DisplayedGem displayedGem, Graphics2D g2d) {
       
        DisplayedGemShape displayedGemShape = displayedGem.getDisplayedGemShape();
       
        if (displayedGemShape instanceof DisplayedGemShape.Triangular) {
            paintBodyTriangle(displayedGem, g2d);

        } else if (displayedGemShape instanceof DisplayedGemShape.Oval) {

            Color gemColour = getBaseGemColour(displayedGem);
            Paint paint;
           
            if (tableTop.getTargetDisplayedCollector() == displayedGem) {
                Rectangle bodyRect = displayedGem.getDisplayedBodyPart().getBounds();
                Paint redGradientPaint = new GradientPaint(0, 0, Color.RED, 25, 32, getTableTopBaseColour(), true);
                Paint gemColourGradientPaint = new GradientPaint(0, 0, new Color(5, 5, 5, 200), 25, 32, new Color(5, 5, 5, 75), true);

                BufferedImage image = getTargetImage(bodyRect.width, bodyRect.height, gemColourGradientPaint, redGradientPaint);
                paint = new TexturePaint(image, bodyRect);
           
            } else {
                paint = gemColour;
            }
            paintBodyOval(displayedGem, paint, g2d);
               
        } else if (displayedGemShape instanceof DisplayedGemShape.Rectangular){
            paintBodyRectangle(displayedGem, g2d);
       
        } else {
            throw new IllegalArgumentException("Don't know how to paint this gem: " + displayedGem.getClass());
        }
    }
   
    /**
     * Paint the body of the displayed gem as an oval shape.
     * @param displayedGem
     * @param g2d
     */
    private void paintBodyOval(DisplayedGem displayedGem, Paint paint, Graphics2D g2d) {
        // The main Gem shape
        Shape bodyShape = displayedGem.getBodyShape();

        // save rendering hints, turn on anti-aliasing so that round edges look round.
        Object oldAntiAliasRenderingHint = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // Fill the Gem
        g2d.setPaint(paint);
        g2d.fill(bodyShape);

        // Draw the solid outline of the Gem
        g2d.setColor(Color.black);
        g2d.draw(bodyShape);

        // restore old rendering hint
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAntiAliasRenderingHint);
    }

    /**
     * Paint the body of the displayed gem as an triangle (trillion?) shape.
     * @param displayedGem
     * @param g2d
     */
    private void paintBodyTriangle(DisplayedGem displayedGem, Graphics2D g2d) {
        Color gemColour = getGemColour(displayedGem);
        Color baseColour = getTableTopBaseColour();

        // The main Gem triangle
        Polygon gemTri = (Polygon)displayedGem.getBodyShape();
        int[] triAxs = gemTri.xpoints;
        int[] triAys = gemTri.ypoints;

        int width = triAxs[1] - triAxs[2];
        int height = triAys[2] - triAys[1];
       
        // The 'facets' triangle
        Polygon facetTri = new Polygon();
       
        // Calculate the distance between the right outer vertex, and the right 'facet' vertex
        int rightVertexDifference = (int) (Math.sqrt( width * width + height * height ) * (DisplayConstants.DIAGONAL_BEVEL_SIZE) / height);
       
        // Find the Left Hand Side Perpendicular difference
        int LHSPerpendicular = (DisplayConstants.BEVEL_WIDTH_X + rightVertexDifference) * height / width;
       
        // Find the points for the inner triangle
        Point triB1 = new Point(triAxs[0] + DisplayConstants.BEVEL_WIDTH_X, triAys[0] + LHSPerpendicular);
        Point triB2 = new Point(triAxs[1] - rightVertexDifference, triAys[1]);
        Point triB3 = new Point(triAxs[2] + DisplayConstants.BEVEL_WIDTH_X, triAys[2] - LHSPerpendicular);
       
        facetTri.addPoint(triB1.x, triB1.y);
        facetTri.addPoint(triB2.x, triB2.y);
        facetTri.addPoint(triB3.x, triB3.y);

        // Fill the Gem
        Shape oldClip = g2d.getClip();
        Area clipArea = new Area(oldClip);
        clipArea.subtract(new Area(facetTri));
        g2d.setClip(clipArea);
        Paint gradient = new GradientPaint(0, 0, gemColour, 15, 25, baseColour, true);
        g2d.setPaint(gradient);
        g2d.fill(gemTri);
        g2d.setClip(oldClip);
       
        // Draw and fill the facet triangle
        Color highlightColour = getGemHighlightColour(displayedGem);
        if (tableTop.getTableTopPanel().isPhotoLook()) {
            g2d.setPaint(highlightColour);
        } else {   
            Paint gradient2 = new GradientPaint(0, 0, highlightColour, 25, 32, baseColour, true);
            g2d.setPaint(gradient2);
        }
        g2d.fill(facetTri);
       
        g2d.setColor(baseColour);
       
        // NOTE: Using the default line width or thinner seems to cause the vertical left edge of the
        //      gem and the vertical left edge of the facet triangle to be drawn improperly (or not at all)
        //      when using JDK 1.4.0.  Making the line width slightly greater than the default seems
        //      to fix this problem???
        // Set the stroke to have a line width that is slightly wider than the default. 
        g2d.setStroke(new BasicStroke(1.000001f));

        g2d.draw(facetTri);

        // Draw the three 'edges'
        g2d.drawLine(triAxs[0], triAys[0], triB1.x, triB1.y);
        g2d.drawLine(triAxs[1], triAys[1], triB2.x, triB2.y);
        g2d.drawLine(triAxs[2], triAys[2], triB3.x, triB3.y);

        // Draw the solid outline of the Gem
        if (tableTop.getTableTopPanel().isPhotoLook()) {
            g2d.setColor(baseColour);
        } else {   
            g2d.setColor(Color.black);
        }
        g2d.draw(gemTri);
    }
   
    /**
     * Paint the body of the displayed gem as an triangle (emerald?) shape.
     * @param displayedGem
     * @param g2d
     */
    private void paintBodyRectangle(DisplayedGem displayedGem, Graphics2D g2d) {
        // The main Gem rectangle
        Polygon gemRect = (Polygon)displayedGem.getBodyShape();
        int[] rectAxs = gemRect.xpoints;
        int[] rectAys = gemRect.ypoints;

        // Fill the Gem
        Color baseColour = getTableTopBaseColour();
        Paint gradient = new GradientPaint(0, 0, getGemColour(displayedGem), 15, 25, baseColour, true);
        g2d.setPaint(gradient);
        g2d.fill(gemRect);

        // Draw the 'facets'
        Polygon facetRect = new Polygon();
        Point rectB1 = new Point(rectAxs[0] + DisplayConstants.BEVEL_WIDTH_X, rectAys[0] + DisplayConstants.BEVEL_WIDTH_Y);
        Point rectB2 = new Point(rectAxs[1] - DisplayConstants.BEVEL_WIDTH_X, rectAys[1] + DisplayConstants.BEVEL_WIDTH_Y);
        Point rectB3 = new Point(rectAxs[2] - DisplayConstants.BEVEL_WIDTH_X, rectAys[2] - DisplayConstants.BEVEL_WIDTH_Y);
        Point rectB4 = new Point(rectAxs[3] + DisplayConstants.BEVEL_WIDTH_X, rectAys[3] - DisplayConstants.BEVEL_WIDTH_Y);
        facetRect.addPoint(rectB1.x, rectB1.y);
        facetRect.addPoint(rectB2.x, rectB2.y);
        facetRect.addPoint(rectB3.x, rectB3.y);
        facetRect.addPoint(rectB4.x, rectB4.y);

        // Draw and fill the facet rectangle
        Paint gradient2 = new GradientPaint(0, 0, getGemHighlightColour(displayedGem), 25, 32, baseColour, true);
        g2d.setPaint(gradient2);
        g2d.fill(facetRect);
        g2d.setColor(baseColour);
        g2d.draw(facetRect);

        // Draw the four 'edges'
        g2d.drawLine(rectAxs[0], rectAys[0], rectB1.x, rectB1.y);
        g2d.drawLine(rectAxs[1], rectAys[1], rectB2.x, rectB2.y);
        g2d.drawLine(rectAxs[2], rectAys[2], rectB3.x, rectB3.y);
        g2d.drawLine(rectAxs[3], rectAys[3], rectB4.x, rectB4.y);

        // Draw the solid outline of the Gem
        if (tableTop.getTableTopPanel().isPhotoLook()) {
            g2d.setColor(DisplayConstants.TRANSPARENT_WHITE);
        } else {
            g2d.setColor(Color.black);
        }
        g2d.draw(gemRect);

    }
   
    /**
     * Paint the indicated input part on this displayed gem.
     * @param displayedGem the displayed gem in question.
     * @param inputIndex the index of the input to paint.
     * @param graphics2D the graphics context into which to paint.
     */
    private void paintInputPart(DisplayedGem displayedGem, int inputIndex, Graphics2D graphics2D) {
       
        // Create a local copy of the graphics object, which we can modify safely.
        Graphics2D g2d = (Graphics2D)graphics2D.create();
       
        // Set the colour based on the type
        g2d.setColor(tableTop.getTypeColour(displayedGem.getDisplayedInputPart(inputIndex)));
       
        Point connectPoint = displayedGem.getDisplayedGemShape().getInputConnectPoint(inputIndex);
        int x = connectPoint.x;
        int y = connectPoint.y;
        int inBoundsWidth = displayedGem.getDisplayedGemShape().getInBounds().width;

        Gem inputGem = displayedGem.getGem();
        Gem.PartInput input = inputGem.getInputPart(inputIndex);
       
        // Draw the input burnt if it's, er... burnt
        if (input.isBurnt()) {
            // Burnt
            // Draw the 'split ends' effect
            g2d.setColor(Color.black);
            g2d.drawArc(x, y - DisplayConstants.ARGUMENT_SPACING/2, inBoundsWidth * 2, DisplayConstants.ARGUMENT_SPACING/2, -135, 45);
            g2d.drawArc(x, y, inBoundsWidth * 2, DisplayConstants.ARGUMENT_SPACING/2, 135, -45);
           
        } else {
            // Not burnt   
            // The line
            g2d.drawLine((int)(x + DisplayConstants.BIND_POINT_RADIUS * 2.5), y, x + inBoundsWidth, y);

            // If the current inputPart that we are looking at is not the intellicutPart, or if we are not
            // pulsing, then we go with normal input drawing, else we draw the input a bit more emphasized.
            Shape inputShape;
            IntellicutManager intellicutManager = tableTop.getIntellicutManager();
            if ((intellicutManager.getIntellicutPart() == displayedGem.getDisplayedInputPart(inputIndex)) &&
                (intellicutManager.getIntellicutMode() == IntellicutMode.PART_INPUT)) {

                // Use special polygon for this input drawing.
                inputShape = getIntellicutInputShape(connectPoint);
               
            } else {
                // Normal input shape.
                inputShape = DisplayedGemShape.getInputShape(connectPoint);
            }
           
            // Antialias input rendering.
            // Also, set stroke control to pure, otherwise circles look deformed.
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
           
            if (input.isConnected()) {
                // fill in the shape.
                g2d.fill(inputShape);
               
            } else {
                CollectorGem inputArgumentTarget = GemGraph.getInputArgumentTarget(input);
                if (inputArgumentTarget == tableTop.getTargetCollector()) {
                   
                    // fill in the shape, and circle it red.
                    g2d.fill(inputShape);
                    g2d.setColor(Color.RED);
                    drawTargetMarker(connectPoint, g2d);
                   
                } else {
                   
                    CollectorGem rootCollector = input.getGem().getRootCollectorGem();
                    boolean isReflecting = rootCollector != null && rootCollector == inputArgumentTarget;
                   
                    if (isReflecting) {
                        // Just draw the outline of the shape.
                        g2d.draw(inputShape);
                       
                    } else {
                        // fill in the shape.
                        g2d.fill(inputShape);
                       
                        // circle it black.
                        if (inputArgumentTarget != null) {
                            g2d.setColor(Color.BLACK);
                            drawTargetMarker(connectPoint, g2d);
                        }
                    }
                }
            }
           
            // Highlight if photolook
            if (tableTop.getTableTopPanel().isPhotoLook()) {
                g2d.setColor(getTableTopBaseColour());
                g2d.draw(inputShape);
            }      
        }
       
        g2d.dispose();
    }
   
    /**
     * Draw the Shape representing the target marker for this Gem's nth input.
     * @param bindPoint the location of the bind point
     * @param g2d the graphics object to use.
     */
    private void drawTargetMarker(Point bindPoint, Graphics2D g2d) {
        // Copy the graphics object
        g2d = (Graphics2D)g2d.create();
       
        // Note: we want to use drawOval(), which takes int arguments.
        //   However, we want to draw in between pixels, so we will multiply all coordinates by two, and scale by 0.5.
       
        g2d.scale(0.5, 0.5);
       
        int x = (2 * bindPoint.x);
        int y = (2 * bindPoint.y) - (DisplayConstants.BIND_POINT_RADIUS * 3);   // 3 == 1.5 * 2;
        int diameter = DisplayConstants.BIND_POINT_RADIUS * 6;                  // 6 == 3 * 2
       
        // Draw the shape.
        g2d.drawOval(x, y, diameter, diameter);
       
        // Dispose the graphics copy.
        g2d.dispose();
    }

    /**
     * Get the shape representing an input when affected by Intellicut.
     * @param bindPoint the location of the bind point
     * @return Shape the shape of the bind point
     */
    private Shape getIntellicutInputShape(Point bindPoint){

        int radius = (int)(0.75 * DisplayConstants.BIND_POINT_RADIUS);

        // Where does this blob go vertically
        int x = bindPoint.x + radius;
        int y = bindPoint.y;

        Polygon diamond = new Polygon();
        diamond.addPoint(x + radius, y - 2 * radius);
        diamond.addPoint(x - radius, y);
        diamond.addPoint(x + radius, y + 2 * radius);
        diamond.addPoint(x + 3 * radius, y);

        return diamond;
    }

    /**
     * Paint the output part if any.
     * @param displayedGem DisplayedGem the gem to paint
     * @param g2d Graphics2D the graphics context
     */
    private void paintOutputPart(DisplayedGem displayedGem, Graphics2D g2d) {

        DisplayedPartOutput displayedOutputPart = displayedGem.getDisplayedOutputPart();
        if (displayedOutputPart == null) {
            return;
        }
       
        Color prevColour = g2d.getColor();
       
        Rectangle outRect = displayedGem.getDisplayedGemShape().getOutBounds();
        int centreY = outRect.y + (outRect.height/2);
       
        // Draw the output line and bind point if output connected (colour based on output type)
        g2d.setColor(tableTop.getTypeColour(displayedOutputPart));
        g2d.drawLine(outRect.x - 1, centreY, outRect.x + outRect.width, centreY);
        Polygon bindPoint = displayedGem.getDisplayedGemShape().getOutputShape();
       
        g2d.fill(bindPoint);
       
        if (tableTop.getTableTopPanel().isPhotoLook()) {
            // Highlight arrow
            g2d.setColor(DisplayConstants.TRANSPARENT_WHITE);
            g2d.draw(bindPoint);
        }  

        g2d.setColor(prevColour);
    }
   
    /**
     * Paint a Connection in the given graphics context
     * @param dConn DisplayedConnection the connection to paint
     * @param g2d Graphics2D the graphics context
     */
    void paintConnection(DisplayedConnection dConn, Graphics2D g2d) {
        // Make a ConnectionRoute
        ConnectionRoute route = dConn.getConnectionRoute();

        // Get the ConnectionRoute's bounding rectangle
        Rectangle bounds = route.getBoundingRectangle();
       
        if (bounds.intersects(g2d.getClipBounds())) {
            // Need to draw this connection
            // The colour is the type colour of the source part
            if (!tableTop.isBadDisplayedConnection(dConn)) {
                g2d.setColor(tableTop.getTypeColour(dConn.getSource()));
            } else {
                g2d.setColor(Color.red);
            }
           
            // Finally draw it
            route.draw(g2d);
        }  
    }  
   
    /**
     * Get the base colour used for the given displayed gem.
     * @param displayedGem the displayed gem in question.
     * @return the gem colour.
     */
    private Color getBaseGemColour(DisplayedGem displayedGem) {

        Gem gem = displayedGem.getGem();

        if (gem instanceof CodeGem) {
            // Code gems are green.
            return Color.green;
           
        } else if (gem instanceof CollectorGem) {
            // Collectors are black.
            return Color.BLACK;

        } else if (gem instanceof RecordFieldSelectionGem) {
            // RecordFieldSelection gems are indigo-ish.
            return Color.decode("0x8000F0");

        } else if (gem instanceof RecordCreationGem) {
            // Record creation gems are magenta
            return Color.MAGENTA;
           
        } else if (gem instanceof FunctionalAgentGem) {
            // Data constructors are orange.
            if (((FunctionalAgentGem)gem).getGemEntity().isDataConstructor()) {
                return Color.orange;
            }

            // Other functional agents are red
            return Color.red;
           
        } else if (gem instanceof ReflectorGem) {
            // Reflectors reflecting the target are pink (somewhere between red and white)..
            if (((ReflectorGem)gem).getCollector() == tableTop.getTargetCollector()) {
                return Color.pink;
            }
           
            // Normal reflectors are white.
            return Color.white;

        } else if (gem instanceof ValueGem) {
            // if the value gem is not editable display it in gray
            if (!tableTop.getTableTopPanel().getValueEntryPanel((ValueGem)gem).isEditable()) {
                return Color.gray;
            }

            // editable value gems are blue
            return Color.blue;

        } else {
            throw new IllegalArgumentException("Unrecognized displayed gem type: " + displayedGem.getClass());
        }
    }
   
    /**
     * Get the colour used as the body colour for the given displayed gem.
     * @param displayedGem the displayed gem in question.
     * @return the gem colour.
     */
    private Color getGemColour(DisplayedGem displayedGem) {

        Color baseGemColour = getBaseGemColour(displayedGem);

        if (tableTop.getTableTopPanel().isPhotoLook()) {
            return GemCutterPaintHelper.getAlphaColour(baseGemColour, DisplayConstants.GEM_FACET_TRANSPARENCY);

        } else {
            return baseGemColour;
        }
    }
   
    /**
     * Get the colour used as the base colour for the tabletop.
     * @return the base colour.
     */
    private Color getTableTopBaseColour() {
        if (tableTop.getTableTopPanel().isPhotoLook()) {
            return DisplayConstants.TRANSPARENT_WHITE;
        } else {
            return Color.WHITE;
        }
    }
    /**
     * Get the colour used as the base colour for the given displayed gem.
     * @param displayedGem the displayed gem in question.
     * @return the base colour.
     */
    private Color getCrackColour(DisplayedGem displayedGem) {

        if (tableTop.getTableTopPanel().isPhotoLook()) {
       
            if (displayedGem.getGem() instanceof ReflectorGem) {
                return GemCutterPaintHelper.getAlphaColour(Color.LIGHT_GRAY, DisplayConstants.GEM_TRANSPARENCY);

            } else {
                return getTableTopBaseColour();
            }

        } else {
            return PLAIN_CRACK_COLOUR;
        }  
    }
   
    /**
     * Get the colour used for highlighting the given displayed gem.
     * @param displayedGem the displayed gem in question.
     * @return the highlight colour.
     */
    private Color getGemHighlightColour(DisplayedGem displayedGem) {
        // Need to come up with a lighter colour of gemColour for highlighting purposes,
        //   and as the inner colour of the Gem (to make it easier to read the Gem name).
        // TEMP: Michael - Need to come up with a better way of brightening up colours.
        Color baseGemColour = getBaseGemColour(displayedGem);
       
        Color gemHighlightColour = new Color(Math.max(110, baseGemColour.getRed()), Math.max(110, baseGemColour.getGreen()), Math.max(110, baseGemColour.getBlue()));
        if (tableTop.getTableTopPanel().isPhotoLook()) {
            gemHighlightColour = GemCutterPaintHelper.getAlphaColour(gemHighlightColour, DisplayConstants.GEM_TRANSPARENCY);
        }
       
        return gemHighlightColour;
    }
   
    /**
     * Get the colour used for the halo for the given displayed gem.
     * @param displayedGem the displayed gem in question.
     * @return the halo colour.
     */
    private Color getGemHaloColour(DisplayedGem displayedGem) {
        if (tableTop.getTableTopPanel().isPhotoLook()) {
            return DisplayConstants.TRANSPARENT_WHITE;
        } else {
            return getGemHighlightColour(displayedGem);
        }
    }
   
    /**
     * Return the color with which to display the gem's text.
     * @param displayedGem the displayed gem in question.
     * @return the gem colour.
     */
    private static Color getTextColour(DisplayedGem displayedGem) {
        if (displayedGem.getGem() instanceof CollectorGem) {
            return Color.WHITE;
        } else {
            return Color.BLACK;
        }
    }
   
    /**
     * {@inheritDoc}
     */
    public void gemLocationChanged(DisplayedGemLocationEvent e) {
        // Invalidate cache.  Only really needed for input/output gems
        // Our local use of absolute coords in the 'crazy paving' for broken FunctionalAgentGems
        // forces us to override this and flush the cached object on a move as well as a resize.
        DisplayedGem dGemSource = (DisplayedGem)e.getSource();
        crazyPavingCache.remove(dGemSource);
    }

    /**
     * {@inheritDoc}
     */
    public void gemSizeChanged(DisplayedGemSizeEvent e) {
        DisplayedGem displayedGem = (DisplayedGem)e.getSource();

        tapeLabelCache.remove(displayedGem);
        runHaloCache.remove(displayedGem);
        selectHaloCache.remove(displayedGem);
    }
}
TOP

Related Classes of org.openquark.gems.client.TableTopGemPainter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.