You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by jk...@apache.org on 2007/04/24 05:08:26 UTC

svn commit: r531707 - in /tapestry/tapestry4/trunk: tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/ tapestry-contrib/src/test/org/apache/tapestry/contrib/services/ tapestry-examples/TimeTracker/ tapestry-examples/TimeTracker/src/co...

Author: jkuhnert
Date: Mon Apr 23 20:08:25 2007
New Revision: 531707

URL: http://svn.apache.org/viewvc?view=rev&rev=531707
Log:
Added a few more abilities to the rounded corner service - like drop shadows.

Added:
    tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/ShadowRenderer.java   (with props)
    tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/
    tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/Locale.java   (with props)
    tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/Locale.jwc
Modified:
    tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/RoundedCornerGenerator.java
    tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/RoundedCornerService.java
    tapestry/tapestry4/trunk/tapestry-contrib/src/test/org/apache/tapestry/contrib/services/TestRoundedCornerService.java
    tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/pom.xml
    tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/LocaleList.html
    tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/WEB-INF/timetracker.application

Modified: tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/RoundedCornerGenerator.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/RoundedCornerGenerator.java?view=diff&rev=531707&r1=531706&r2=531707
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/RoundedCornerGenerator.java (original)
+++ tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/RoundedCornerGenerator.java Mon Apr 23 20:08:25 2007
@@ -1,10 +1,13 @@
 package org.apache.tapestry.contrib.services.impl;
 
+import org.apache.hivemind.util.Defense;
+
 import javax.imageio.ImageIO;
 import java.awt.*;
 import java.awt.geom.Arc2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
 import java.awt.image.BufferedImage;
-import java.io.ByteArrayOutputStream;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -13,6 +16,16 @@
  */
 public class RoundedCornerGenerator {
 
+    public static final String TOP_LEFT = "tl";
+    public static final String TOP_RIGHT = "tr";
+    public static final String BOTTOM_LEFT = "bl";
+    public static final String BOTTOM_RIGHT = "br";
+
+    public static final String LEFT = "left";
+    public static final String RIGHT = "right";
+    public static final String TOP = "top";
+    public static final String BOTTOM = "bottom";
+
     // css2 color spec - http://www.w3.org/TR/REC-CSS2/syndata.html#color-units
     private static final Map _cssSpecMap = new HashMap();
 
@@ -33,73 +46,345 @@
         _cssSpecMap.put("teal", new Color(0,128,128));
         _cssSpecMap.put("white", Color.white);
         _cssSpecMap.put("yellow", Color.yellow);
+
+        ImageIO.setUseCache(false);
     }
 
-    public static final String TOP_LEFT = "tl";
-    public static final String TOP_RIGHT = "tr";
-    public static final String BOTTOM_LEFT = "bl";
-    public static final String BOTTOM_RIGHT = "br";
+    private static Color SHADOW_COLOR = new Color(0x000000);
 
-    // holds pre-built binaries for previously generated colors
-    private Map _imageCache = new HashMap();
+    private static final float DEFAULT_OPACITY = 0.5f;
 
-    public byte[] buildCorner(String color, String backgroundColor, int width, int height, String angle)
+    private static final float ANGLE_TOP_LEFT = 90f;
+    private static final float ANGLE_TOP_RIGHT = 0f;
+    private static final float ANGLE_BOTTOM_LEFT = 180f;
+    private static final float ANGLE_BOTTOM_RIGHT = 270f;
+
+    public BufferedImage buildCorner(String color, String backgroundColor, int width, int height,
+                                     String angle, int shadowWidth, float endOpacity)
     throws Exception
     {
-        String hashKey = color + backgroundColor + width + height + angle;
+        float startAngle = getStartAngle(angle);
+        Color bgColor = backgroundColor == null ? null : decodeColor(backgroundColor);
+
+        if (shadowWidth <= 0) {
+
+            BufferedImage arc = drawArc(color, backgroundColor, width, height, angle, false, -1);
+            BufferedImage ret = arc;
+
+            if (bgColor != null) {
+                
+                ret = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+                Graphics2D g2 = (Graphics2D)ret.createGraphics();
+                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+                Arc2D.Float arcArea = new Arc2D.Float(0, 0, width, height, startAngle, 90, Arc2D.PIE);
+
+                g2.setColor(bgColor);
+                g2.fill(arcArea.getBounds2D());
+
+                g2.drawImage(arc, 0, 0, null);
+                
+                g2.dispose();
+            }
 
-        byte[] ret = (byte[])_imageCache.get(hashKey);
-        if (ret != null)
             return ret;
-        
+        }
+
+        BufferedImage mask = drawArc(color, backgroundColor, width, height, angle, true, shadowWidth);
+        BufferedImage arc = drawArc(color, backgroundColor, width, height, angle, false, shadowWidth);
+
+        float startX = 0;
+        float startY = 0;
+        int shadowSize = shadowWidth * 2;
+        float canvasWidth = width + (shadowSize * 2);
+        float canvasHeight = height + (shadowSize * 2);
+
+        if (startAngle == ANGLE_BOTTOM_LEFT) {
+
+            startY -= (shadowSize * 2);
+
+        } else if (startAngle == ANGLE_TOP_RIGHT) {
+
+            startX -= shadowSize * 2;
+
+        } else if (startAngle == ANGLE_BOTTOM_RIGHT) {
+
+            startX -= shadowSize * 2;
+            startY -= shadowSize * 2;
+            // startX -= shadowSize;
+            // startY -= shadowSize;
+        }
+
+        BufferedImage ret = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g2 = (Graphics2D)ret.createGraphics();
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+        Arc2D.Float arcArea = new Arc2D.Float(startX, startY, canvasWidth, canvasHeight, startAngle, 90, Arc2D.PIE);
+
+        if (bgColor != null) {
+
+            g2.setColor(bgColor);
+            g2.fill(arcArea.getBounds2D());
+        }
+
+        BufferedImage shadow = drawArcShadow(mask, color, backgroundColor, width, height, angle, shadowWidth, endOpacity);
+
+        g2.setClip(arcArea);
+        g2.drawImage(shadow, 0, 0, null);
+
+        g2.setClip(null);
+        g2.drawImage(arc, 0, 0, null);
+
+        return ret;
+    }
+
+    BufferedImage drawArc(String color, String backgroundColor, int width, int height, String angle, boolean masking, int shadowWidth)
+    {
         Color arcColor = decodeColor(color);
         Color bgColor = backgroundColor == null ? null : decodeColor(backgroundColor);
         float startAngle = getStartAngle(angle);
 
-        BufferedImage img = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB);
-        Graphics2D g2 = (Graphics2D) img.createGraphics();
+        int canvasWidth = width;
+        int canvasHeight = height;
+        float startX = 0;
+        float startY = 0;
+        int shadowSize = 0;
 
-        Arc2D.Float fillArea = new Arc2D.Float(0f, 0f, (float)width, (float)height, startAngle, 90, Arc2D.PIE);
-        
-        // fill background color
+        if (shadowWidth > 0 && !masking) {
 
-        if (bgColor != null) {
-            
-            g2.setClip(fillArea.getBounds2D());
-            g2.setColor(bgColor);
-            g2.fillRect(0, 0, width, height);
+            shadowSize = shadowWidth * 2;
+            canvasWidth += shadowSize * 2;
+            canvasHeight += shadowSize * 2;
+
+            if (startAngle == ANGLE_TOP_LEFT) {
+
+                startX += shadowSize;
+                startY += shadowSize;
+
+            } else if (startAngle == ANGLE_BOTTOM_LEFT) {
+
+                startX += shadowSize;
+                startY -= shadowSize;
+
+            } else if (startAngle == ANGLE_TOP_RIGHT) {
+
+                startX -= shadowSize;
+                startY += shadowSize;
+                
+            } else if (startAngle == ANGLE_BOTTOM_RIGHT) {
+                
+                startX -= shadowSize;
+                startY -= shadowSize;
+            }
         }
 
-        // draw arc
+        BufferedImage img = new BufferedImage( canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g2 = (Graphics2D) img.createGraphics();
+
+        float extent = 90;
+        if (masking) {
+            
+            extent = 120;
+            startAngle -= 20;
+        }
         
-        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        Arc2D.Float fillArea = new Arc2D.Float(startX, startY, width, height, startAngle, extent, Arc2D.PIE);
         
+        // draw arc
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
         g2.setColor(arcColor);
         g2.setComposite(AlphaComposite.Src);
         g2.fill(fillArea);
+
+        g2.dispose();
+
+        return img;
+    }
+
+    BufferedImage drawArcShadow(BufferedImage mask, String color, String backgroundColor, int width, int height,
+                                String angle, int shadowWidth, float endOpacity)
+    {
+        float startAngle = getStartAngle(angle);
+        int shadowSize = shadowWidth * 2;
+        int sampleY = 0;
+        int sampleX = 0;
+        int sampleWidth = width + shadowSize;
+        int sampleHeight = height + shadowSize;
+
+        if (startAngle == ANGLE_TOP_LEFT) {
+
+        } else if (startAngle == ANGLE_BOTTOM_LEFT) {
+
+            sampleWidth -= shadowSize;
+            sampleHeight = height;
+
+            sampleY += shadowSize;
+
+        } else if (startAngle == ANGLE_TOP_RIGHT) {
+
+            sampleWidth -= shadowSize;
+            sampleHeight -= shadowSize;
+
+            sampleX += shadowSize;
+        } else if (startAngle == ANGLE_BOTTOM_RIGHT) {
+
+            sampleWidth -= shadowSize;
+            sampleHeight -= shadowSize;
+
+            sampleX += shadowSize;
+            sampleY += shadowSize;
+        }
+
+        ShadowRenderer shadowRenderer = new ShadowRenderer(shadowWidth, endOpacity, SHADOW_COLOR);
+        BufferedImage dropShadow = shadowRenderer.createShadow(mask);
+
+        // draw shadow arc
+
+        BufferedImage img = new BufferedImage( (width * 4), (height * 4), BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g2 = (Graphics2D) img.createGraphics();
         
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g2.setComposite(AlphaComposite.Src);
+        g2.drawImage(dropShadow, 0, 0, null);
+
         g2.dispose();
 
-        ByteArrayOutputStream bo = null;
+        return img.getSubimage(sampleX, sampleY, sampleWidth, sampleHeight);
+    }
 
-        try {
-            ImageIO.setUseCache(false);
+    public BufferedImage buildShadow(String backgroundColor, int width, int height,
+                                     float arcWidth, float arcHeight,
+                                     int shadowWidth, float endOpacity)
+    {
+        Color bgColor = backgroundColor == null ? null : decodeColor(backgroundColor);
+
+        BufferedImage mask = new BufferedImage(width, height,  BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g2 = mask.createGraphics();
+
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        
+        RoundRectangle2D.Float fillArea = new RoundRectangle2D.Float(0, 0, width, height, arcHeight, arcWidth);
+        g2.fill(fillArea);
+        g2.dispose();
 
-            bo = new ByteArrayOutputStream();
+        // clip shadow
 
-            ImageIO.write(img, "gif", bo);
+        ShadowRenderer shadowRenderer = new ShadowRenderer(shadowWidth, endOpacity, SHADOW_COLOR);
+        BufferedImage dropShadow = shadowRenderer.createShadow(mask);
 
-            ret = bo.toByteArray();
+        BufferedImage clipImg = new BufferedImage( width + (shadowWidth * 2), height + (shadowWidth * 2), BufferedImage.TYPE_INT_ARGB);
+        g2 = clipImg.createGraphics();
 
-            _imageCache.put(hashKey, ret);
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g2.setComposite(AlphaComposite.Src);
 
-            return ret;
+        RoundRectangle2D.Float clip = new RoundRectangle2D.Float(0, 0, width + (shadowWidth * 2), height + (shadowWidth * 2), arcHeight, arcWidth);
+        g2.setClip(clip);
+        g2.drawImage(dropShadow, 0, 0, null);
+        g2.dispose();
+        
+        // draw everything
 
-        } finally {
-            if (bo != null) {
-                bo.close();
-            }
+        BufferedImage img = new BufferedImage( width + (shadowWidth * 2), height + (shadowWidth * 2), BufferedImage.TYPE_INT_ARGB);
+        g2 = img.createGraphics();
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+        if (bgColor != null) {
+
+            fillArea = new RoundRectangle2D.Float(0, 0, width + (shadowWidth * 2), height + (shadowWidth * 2), arcHeight, arcWidth);
+            g2.setColor(bgColor);
+            g2.fill(fillArea.getBounds2D());
+        }
+
+        g2.drawImage(clipImg, 0, 0, null);
+
+        if (bgColor != null) {
+
+            fillArea = new RoundRectangle2D.Float(0, 0, width, height, arcHeight, arcWidth);
+            g2.setColor(bgColor);
+            g2.fill(fillArea);
+        }
+
+        g2.dispose();
+
+        return img;
+    }
+
+    public BufferedImage buildSideShadow(String side, int size, float opacity)
+    throws Exception
+    {
+        Defense.notNull(side, "side");
+        
+        if (opacity <= 0)
+            opacity = DEFAULT_OPACITY;
+
+        int maskWidth = 0;
+        int maskHeight = 0;
+        int sampleY = 0;
+        int sampleX = 0;
+        int sampleWidth = 0;
+        int sampleHeight = 0;
+
+        if (LEFT.equals(side)) {
+
+            maskWidth = size * 4;
+            maskHeight = size * 4;
+            sampleY = maskHeight / 2;
+            sampleWidth = size * 2;
+            sampleHeight = 2;
+        } else if (RIGHT.equals(side)) {
+            
+            maskWidth = size * 4;
+            maskHeight = size * 4;
+            sampleY = maskHeight / 2;
+            sampleX = maskWidth;
+            sampleWidth = size * 2;
+            sampleHeight = 2;
+        } else if (BOTTOM.equals(side)) {
+
+            maskWidth = size * 4;
+            maskHeight = size * 4;
+            sampleY = maskHeight;
+            sampleX = maskWidth / 2;
+            sampleWidth = 2;
+            sampleHeight = size * 2;
+        } else if (TOP.equals(side)) {
+
+            maskWidth = size * 4;
+            maskHeight = size * 4;
+            sampleY = 0;
+            sampleX = maskWidth / 2;
+            sampleWidth = 2;
+            sampleHeight = size * 2;
         }
+        
+        BufferedImage mask = new BufferedImage( maskWidth, maskHeight, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g2 = (Graphics2D) mask.createGraphics();
+
+        g2.setColor(Color.white);
+        g2.fillRect(0, 0, maskWidth, maskHeight);
+
+        g2.dispose();
+
+        ShadowRenderer shadowRenderer = new ShadowRenderer(size, opacity, SHADOW_COLOR);
+        BufferedImage dropShadow = shadowRenderer.createShadow(mask);
+
+        BufferedImage render = new BufferedImage(maskWidth * 2, maskHeight * 2, BufferedImage.TYPE_INT_ARGB);
+        g2 = (Graphics2D)render.createGraphics();
+
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        
+        Rectangle2D.Float clip = new Rectangle2D.Float(sampleX, sampleY, sampleWidth, sampleHeight);
+
+        g2.setColor(Color.white);
+        g2.fill(clip);
+        
+        g2.drawImage(dropShadow, 0, 0, null);
+
+        g2.dispose();
+
+        return render.getSubimage(sampleX, sampleY, sampleWidth, sampleHeight);
     }
 
     /**
@@ -112,15 +397,15 @@
     public float getStartAngle(String code)
     {
         if (TOP_LEFT.equalsIgnoreCase(code))
-            return 90f;
+            return ANGLE_TOP_LEFT;
         if (TOP_RIGHT.equalsIgnoreCase(code))
-            return 0f;
+            return ANGLE_TOP_RIGHT;
         if (BOTTOM_LEFT.equalsIgnoreCase(code))
-            return 180f;
+            return ANGLE_BOTTOM_LEFT;
         if (BOTTOM_RIGHT.equalsIgnoreCase(code))
-            return 270f;
+            return ANGLE_BOTTOM_RIGHT;
 
-        return 0f;
+        return ANGLE_TOP_RIGHT;
     }
 
     /**

Modified: tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/RoundedCornerService.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/RoundedCornerService.java?view=diff&rev=531707&r1=531706&r2=531707
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/RoundedCornerService.java (original)
+++ tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/RoundedCornerService.java Mon Apr 23 20:08:25 2007
@@ -11,7 +11,10 @@
 import org.apache.tapestry.web.WebRequest;
 import org.apache.tapestry.web.WebResponse;
 
+import javax.imageio.ImageIO;
 import javax.servlet.http.HttpServletResponse;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.HashMap;
@@ -31,6 +34,14 @@
     public static final String PARM_HEIGHT = "h";
     public static final String PARM_ANGLE = "a";
 
+    public static final String PARM_SHADOW_WIDTH ="sw";
+    public static final String PARM_SHADOW_OPACITY ="o";
+    public static final String PARM_SHADOW_SIDE = "s";
+
+    public static final String PARM_WHOLE_SHADOW = "shadow";
+    public static final String PARM_ARC_HEIGHT = "ah";
+    public static final String PARM_ARC_WIDTH = "aw";
+
     private static final long MONTH_SECONDS = 60 * 60 * 24 * 30;
 
     private static final long EXPIRES = System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000L;
@@ -44,7 +55,10 @@
     private WebResponse _response;
 
     private RoundedCornerGenerator _generator = new RoundedCornerGenerator();
-    
+
+    // holds pre-built binaries for previously generated colors
+    private Map _imageCache = new HashMap();
+
     public ILink getLink(boolean post, Object parameter)
     {
         Defense.notNull(parameter, "parameter");
@@ -62,31 +76,63 @@
     public void service(IRequestCycle cycle)
             throws IOException
     {
+        if (_request.getHeader("If-Modified-Since") != null)
+        {
+            _response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+            return;
+        }
+
         String color = cycle.getParameter(PARM_COLOR);
         String bgColor = cycle.getParameter(PARM_BACKGROUND_COLOR);
         int width = getIntParam(cycle.getParameter(PARM_WIDTH));
         int height = getIntParam(cycle.getParameter(PARM_HEIGHT));
         String angle = cycle.getParameter(PARM_ANGLE);
+        
+        int shadowWidth = getIntParam(cycle.getParameter(PARM_SHADOW_WIDTH));
+        float shadowOpacity = getFloatParam(cycle.getParameter(PARM_SHADOW_OPACITY));
+        String side = cycle.getParameter(PARM_SHADOW_SIDE);
 
-        OutputStream os = null;
+        boolean wholeShadow = Boolean.valueOf(cycle.getParameter(PARM_WHOLE_SHADOW)).booleanValue();
+        float arcWidth = getFloatParam(cycle.getParameter(PARM_ARC_WIDTH));
+        float arcHeight = getFloatParam(cycle.getParameter(PARM_ARC_HEIGHT));
 
+        String hashKey = color + bgColor + width + height + angle + shadowWidth + shadowOpacity + side + wholeShadow;
+
+        ByteArrayOutputStream bo = null;
+        
         try {
+            
+            String type = (bgColor != null) ? "gif" : "png";
 
-            if (_request.getHeader("If-Modified-Since") != null)
-            {
-                _response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+            byte[] data = (byte[])_imageCache.get(hashKey);
+            if (data != null) {
+
+                writeImageResponse(data, type);
                 return;
             }
 
-            byte[] data = _generator.buildCorner(color, bgColor, width, height, angle);
+            BufferedImage image = null;
 
-            _response.setDateHeader("Expires", EXPIRES);
-            _response.setHeader("Cache-Control", "public, max-age=" + (MONTH_SECONDS * 3));
-            _response.setContentLength(data.length);
-            
-            os = _response.getOutputStream(new ContentType("image/gif"));
+            if (wholeShadow) {
 
-            os.write(data);
+                image = _generator.buildShadow(bgColor, width, height, arcWidth, arcHeight, shadowWidth, shadowOpacity);
+            } else if (side != null) {
+
+                image = _generator.buildSideShadow(side, shadowWidth, shadowOpacity);
+            } else {
+
+                image = _generator.buildCorner(color, bgColor, width, height, angle, shadowWidth, shadowOpacity);
+            }
+
+            bo = new ByteArrayOutputStream();
+
+            ImageIO.write(image, type, bo);
+
+            data = bo.toByteArray();
+
+            _imageCache.put(hashKey, data);
+
+            writeImageResponse(data, type);
             
         } catch (Throwable ex) {
 
@@ -94,6 +140,32 @@
             _exceptionReporter.reportRequestException("Error creating image.", ex);
         } finally {
             try {
+                if (bo != null) {
+                    bo.close();
+                }
+            } catch (Throwable t) {
+                // ignore
+            }
+
+        }
+    }
+
+    void writeImageResponse(byte[] data, String type)
+    throws Exception
+    {
+        OutputStream os = null;
+
+        try {
+            _response.setDateHeader("Expires", EXPIRES);
+            _response.setHeader("Cache-Control", "public, max-age=" + (MONTH_SECONDS * 3));
+            _response.setContentLength(data.length);
+
+            os = _response.getOutputStream(new ContentType("image/" + type));
+
+            os.write(data);
+
+        }  finally {
+            try {
                 if (os != null) {
                     os.flush();
                     os.close();
@@ -101,7 +173,6 @@
             } catch (Throwable t) {
                 // ignore
             }
-
         }
     }
 
@@ -111,6 +182,14 @@
             return 0;
         
         return Integer.valueOf(value).intValue();
+    }
+
+    private float getFloatParam(String value)
+    {
+        if (value == null)
+            return 0f;
+        
+        return Float.valueOf(value).floatValue();
     }
 
     public String getName()

Added: tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/ShadowRenderer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/ShadowRenderer.java?view=auto&rev=531707
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/ShadowRenderer.java (added)
+++ tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/ShadowRenderer.java Mon Apr 23 20:08:25 2007
@@ -0,0 +1,357 @@
+package org.apache.tapestry.contrib.services.impl;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+
+/**
+ *
+ */
+public class ShadowRenderer {
+
+    // size of the shadow in pixels (defines the fuzziness)
+    private int size = 5;
+
+    // opacity of the shadow
+    private float opacity = 0.5f;
+    
+    // color of the shadow
+    private Color color = Color.BLACK;
+
+    /**
+     * <p>A shadow renderer needs three properties to generate shadows.
+     * These properties are:</p>
+     * <ul>
+     *   <li><i>size</i>: The size, in pixels, of the shadow. This property also
+     *   defines the fuzzyness.</li>
+     *   <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li>
+     *   <li><i>color</i>: The color of the shadow. Shadows are not meant to be
+     *   black only.</li>
+     * </ul>
+     * @param size the size of the shadow in pixels. Defines the fuzziness.
+     * @param opacity the opacity of the shadow.
+     * @param color the color of the shadow.
+     */
+    public ShadowRenderer(final int size, final float opacity, final Color color) {
+
+        setSize(size);
+        setOpacity(opacity);
+        setColor(color);
+    }
+
+    /**
+     * <p>Gets the color used by the renderer to generate shadows.</p>
+     * @return this renderer's shadow color
+     */
+    public Color getColor() {
+        return color;
+    }
+
+    /**
+     * <p>Sets the color used by the renderer to generate shadows.</p>
+     * <p>Consecutive calls to {@link #createShadow} will all use this color
+     * until it is set again.</p>
+     * <p>If the color provided is null, the previous color will be retained.</p>
+     * @param shadowColor the generated shadows color
+     */
+    public void setColor(final Color shadowColor) {
+        if (shadowColor != null) {
+            Color oldColor = this.color;
+            this.color = shadowColor;
+        }
+    }
+
+    /**
+     * <p>Gets the opacity used by the renderer to generate shadows.</p>
+     * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
+     * transparent and 1.0f fully opaque.</p>
+     * @return this renderer's shadow opacity
+     */
+    public float getOpacity() {
+        return opacity;
+    }
+
+    /**
+     * <p>Sets the opacity used by the renderer to generate shadows.</p>
+     * <p>Consecutive calls to {@link #createShadow} will all use this opacity
+     * until it is set again.</p>
+     * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
+     * transparent and 1.0f fully opaque. If you provide a value out of these
+     * boundaries, it will be restrained to the closest boundary.</p>
+     * @param shadowOpacity the generated shadows opacity
+     */
+    public void setOpacity(final float shadowOpacity) {
+        float oldOpacity = this.opacity;
+
+        if (shadowOpacity < 0.0) {
+            this.opacity = 0.0f;
+        } else if (shadowOpacity > 1.0f) {
+            this.opacity = 1.0f;
+        } else {
+            this.opacity = shadowOpacity;
+        }
+    }
+
+    /**
+     * <p>Gets the size in pixel used by the renderer to generate shadows.</p>
+     * @return this renderer's shadow size
+     */
+    public int getSize() {
+        return size;
+    }
+
+    /**
+     * <p>Sets the size, in pixels, used by the renderer to generate shadows.</p>
+     * <p>The size defines the blur radius applied to the shadow to create the
+     * fuzziness.</p>
+     * <p>There is virtually no limit to the size. The size cannot be negative.
+     * If you provide a negative value, the size will be 0 instead.</p>
+     * @param shadowSize the generated shadows size in pixels (fuzziness)
+     */
+    public void setSize(final int shadowSize) {
+        int oldSize = this.size;
+
+        if (shadowSize < 0) {
+            this.size = 0;
+        } else {
+            this.size = shadowSize;
+        }
+    }
+
+    /**
+     * <p>Generates the shadow for a given picture and the current properties
+     * of the renderer.</p>
+     * <p>The generated image dimensions are computed as following:</p>
+     * <pre>
+     * width  = imageWidth  + 2 * shadowSize
+     * height = imageHeight + 2 * shadowSize
+     * </pre>
+     * @param image the picture from which the shadow must be cast
+     * @return the picture containing the shadow of <code>image</code>
+     */
+    public BufferedImage createShadow(final BufferedImage image) {
+        
+        // Written by Sesbastien Petrucci
+        int shadowSize = size * 2;
+
+        int srcWidth = image.getWidth();
+        int srcHeight = image.getHeight();
+
+        int dstWidth = srcWidth + shadowSize;
+        int dstHeight = srcHeight + shadowSize;
+
+        int left = size;
+        int right = shadowSize - left;
+
+        int yStop = dstHeight - right;
+
+        int shadowRgb = color.getRGB() & 0x00FFFFFF;
+        int[] aHistory = new int[shadowSize];
+        int historyIdx;
+
+        int aSum;
+
+        BufferedImage dst = new BufferedImage(dstWidth, dstHeight,
+                                              BufferedImage.TYPE_INT_ARGB);
+
+        int[] dstBuffer = new int[dstWidth * dstHeight];
+        int[] srcBuffer = new int[srcWidth * srcHeight];
+
+        getPixels(image, 0, 0, srcWidth, srcHeight, srcBuffer);
+
+        int lastPixelOffset = right * dstWidth;
+        float hSumDivider = 1.0f / shadowSize;
+        float vSumDivider = opacity / shadowSize;
+
+        int[] hSumLookup = new int[256 * shadowSize];
+        for (int i = 0; i < hSumLookup.length; i++) {
+            hSumLookup[i] = (int) (i * hSumDivider);
+        }
+
+        int[] vSumLookup = new int[256 * shadowSize];
+        for (int i = 0; i < vSumLookup.length; i++) {
+            vSumLookup[i] = (int) (i * vSumDivider);
+        }
+
+        int srcOffset;
+
+        // horizontal pass : extract the alpha mask from the source picture and
+        // blur it into the destination picture
+        for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) {
+
+            // first pixels are empty
+            for (historyIdx = 0; historyIdx < shadowSize; ) {
+                aHistory[historyIdx++] = 0;
+            }
+
+            aSum = 0;
+            historyIdx = 0;
+            srcOffset = srcY * srcWidth;
+
+            // compute the blur average with pixels from the source image
+            for (int srcX = 0; srcX < srcWidth; srcX++) {
+
+                int a = hSumLookup[aSum];
+                dstBuffer[dstOffset++] = a << 24;   // store the alpha value only
+                                                    // the shadow color will be added in the next pass
+
+                aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum
+
+                // extract the new pixel ...
+                a = srcBuffer[srcOffset + srcX] >>> 24;
+                aHistory[historyIdx] = a;   // ... and store its value into history
+                aSum += a;                  // ... and add its value to the sum
+
+                if (++historyIdx >= shadowSize) {
+                    historyIdx -= shadowSize;
+                }
+            }
+
+            // blur the end of the row - no new pixels to grab
+            for (int i = 0; i < shadowSize; i++) {
+
+                int a = hSumLookup[aSum];
+                dstBuffer[dstOffset++] = a << 24;
+
+                // substract the oldest pixel from the sum ... and nothing new to add !
+                aSum -= aHistory[historyIdx];
+
+                if (++historyIdx >= shadowSize) {
+                    historyIdx -= shadowSize;
+                }
+            }
+        }
+
+        // vertical pass
+        for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
+
+            aSum = 0;
+
+            // first pixels are empty
+            for (historyIdx = 0; historyIdx < left;) {
+                aHistory[historyIdx++] = 0;
+            }
+
+            // and then they come from the dstBuffer
+            for (int y = 0; y < right; y++, bufferOffset += dstWidth) {
+                int a = dstBuffer[bufferOffset] >>> 24;         // extract alpha
+                aHistory[historyIdx++] = a;                     // store into history
+                aSum += a;                                      // and add to sum
+            }
+
+            bufferOffset = x;
+            historyIdx = 0;
+
+            // compute the blur avera`ge with pixels from the previous pass
+            for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) {
+
+                int a = vSumLookup[aSum];
+                dstBuffer[bufferOffset] = a << 24 | shadowRgb;  // store alpha value + shadow color
+
+                aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum
+
+                a = dstBuffer[bufferOffset + lastPixelOffset] >>> 24;   // extract the new pixel ...
+                aHistory[historyIdx] = a;                               // ... and store its value into history
+                aSum += a;                                              // ... and add its value to the sum
+
+                if (++historyIdx >= shadowSize) {
+                    historyIdx -= shadowSize;
+                }
+            }
+
+            // blur the end of the column - no pixels to grab anymore
+            for (int y = yStop; y < dstHeight; y++, bufferOffset += dstWidth) {
+
+                int a = vSumLookup[aSum];
+                dstBuffer[bufferOffset] = a << 24 | shadowRgb;
+
+                aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum
+
+                if (++historyIdx >= shadowSize) {
+                    historyIdx -= shadowSize;
+                }
+            }
+        }
+
+        setPixels(dst, 0, 0, dstWidth, dstHeight, dstBuffer);
+        return dst;
+    }
+
+    /**
+     * <p>Returns an array of pixels, stored as integers, from a
+     * <code>BufferedImage</code>. The pixels are grabbed from a rectangular
+     * area defined by a location and two dimensions. Calling this method on
+     * an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
+     * and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
+     *
+     * @param img the source image
+     * @param x the x location at which to start grabbing pixels
+     * @param y the y location at which to start grabbing pixels
+     * @param w the width of the rectangle of pixels to grab
+     * @param h the height of the rectangle of pixels to grab
+     * @param pixels a pre-allocated array of pixels of size w*h; can be null
+     * @return <code>pixels</code> if non-null, a new array of integers
+     *   otherwise
+     * @throws IllegalArgumentException is <code>pixels</code> is non-null and
+     *   of length &lt; w*h
+     */
+    public static int[] getPixels(BufferedImage img,
+                                  int x, int y, int w, int h, int[] pixels) {
+        if (w == 0 || h == 0) {
+            return new int[0];
+        }
+
+        if (pixels == null) {
+            pixels = new int[w * h];
+        } else if (pixels.length < w * h) {
+            throw new IllegalArgumentException("pixels array must have a length" +
+                                               " >= w*h");
+        }
+
+        int imageType = img.getType();
+        if (imageType == BufferedImage.TYPE_INT_ARGB ||
+            imageType == BufferedImage.TYPE_INT_RGB) {
+            Raster raster = img.getRaster();
+            return (int[]) raster.getDataElements(x, y, w, h, pixels);
+        }
+
+        // Unmanages the image
+        return img.getRGB(x, y, w, h, pixels, 0, w);
+    }
+
+    /**
+     * <p>Writes a rectangular area of pixels in the destination
+     * <code>BufferedImage</code>. Calling this method on
+     * an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
+     * and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
+     *
+     * @param img the destination image
+     * @param x the x location at which to start storing pixels
+     * @param y the y location at which to start storing pixels
+     * @param w the width of the rectangle of pixels to store
+     * @param h the height of the rectangle of pixels to store
+     * @param pixels an array of pixels, stored as integers
+     * @throws IllegalArgumentException is <code>pixels</code> is non-null and
+     *   of length &lt; w*h
+     */
+    public static void setPixels(BufferedImage img,
+                                 int x, int y, int w, int h, int[] pixels) {
+        if (pixels == null || w == 0 || h == 0) {
+            return;
+        } else if (pixels.length < w * h) {
+            throw new IllegalArgumentException("pixels array must have a length" +
+                                               " >= w*h");
+        }
+
+        int imageType = img.getType();
+        if (imageType == BufferedImage.TYPE_INT_ARGB ||
+            imageType == BufferedImage.TYPE_INT_RGB) {
+            WritableRaster raster = img.getRaster();
+            raster.setDataElements(x, y, w, h, pixels);
+        } else {
+            // Unmanages the image
+            img.setRGB(x, y, w, h, pixels, 0, w);
+        }
+    }
+}

Propchange: tapestry/tapestry4/trunk/tapestry-contrib/src/java/org/apache/tapestry/contrib/services/impl/ShadowRenderer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tapestry/tapestry4/trunk/tapestry-contrib/src/test/org/apache/tapestry/contrib/services/TestRoundedCornerService.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-contrib/src/test/org/apache/tapestry/contrib/services/TestRoundedCornerService.java?view=diff&rev=531707&r1=531706&r2=531707
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-contrib/src/test/org/apache/tapestry/contrib/services/TestRoundedCornerService.java (original)
+++ tapestry/tapestry4/trunk/tapestry-contrib/src/test/org/apache/tapestry/contrib/services/TestRoundedCornerService.java Mon Apr 23 20:08:25 2007
@@ -4,31 +4,22 @@
 import org.apache.tapestry.contrib.services.impl.RoundedCornerGenerator;
 import org.testng.annotations.Test;
 
+import java.awt.image.BufferedImage;
+
 /**
  * Tests functionality of {@link org.apache.tapestry.contrib.services.impl.RoundedCornerService}.
  */
 @Test
 public class TestRoundedCornerService extends TestBase {
 
-    public void test_Build_Corner()
-    throws Exception
-    {
-        RoundedCornerGenerator generator = new RoundedCornerGenerator();
 
-        byte[] data = generator.buildCorner("FF7E00", null, 30, 30, "tr");
-
-        assert data != null;
-        assert data.length > 0;        
-    }
-
-    public void test_Corner_Cached()
+    public void test_Build_Corner()
     throws Exception
     {
         RoundedCornerGenerator generator = new RoundedCornerGenerator();
 
-        byte[] data1 = generator.buildCorner("FF7E00", null, 30, 30, "tr");
-        byte[] data2 = generator.buildCorner("FF7E00", null, 30, 30, "tr");
-
-        assert data1 == data2;
+        BufferedImage image = generator.buildCorner("FF7E00", null, 30, 30, "tr", -1, -1);
+        assert image.getWidth() == 30;
+        assert image.getHeight() == 30;
     }
 }

Modified: tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/pom.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/pom.xml?view=diff&rev=531707&r1=531706&r2=531707
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/pom.xml (original)
+++ tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/pom.xml Mon Apr 23 20:08:25 2007
@@ -108,6 +108,13 @@
                     <include>*.properties</include>
                 </includes>
             </resource>
+            <resource>
+                <directory>src/java</directory>
+                <includes>
+                    <include>**/*.page</include>
+                    <include>**/*.jwc</include>
+                </includes>
+            </resource>
         </resources>
         
         <plugins>

Modified: tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/LocaleList.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/LocaleList.html?view=diff&rev=531707&r1=531706&r2=531707
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/LocaleList.html (original)
+++ tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/LocaleList.html Mon Apr 23 20:08:25 2007
@@ -1,22 +1,20 @@
 <span jwcid="@Border">
 
 <p>
-Simple For loop listing locales.
+Simple <code>For</code> loop listing locales.
 </p>
 
 <style>
-	.localeList {
-		/* padding:0.3em; */
+
+    .localeList {
         background: #2A78B0;
         display:block;
 		float:left;
-		width:20%;
+        width:20%;
         margin-top:0.6em;
 		margin-left:0.5em;
     }
-
     .localeList a { color: #fff;}
-
     .localeList p { margin: 0 10px;}
 
     .roundtop {
@@ -40,38 +38,28 @@
         display: block !important;
     }
 
-    .selected {
-		background: #efefef;
-	}
-	
-	.selected a {
-		color: #000;
-	}
+    .selected { background: #efefef; }
+	.selected a { color: #000; }
 	
 	.detail {
-		clear:left;
-		display:block;
-		padding: 1em;
-		width: 56%;
-		margin-top:1em;
-		border-left: 1px solid #efefef;
-		border-top: 1px solid #efefef;
-		border-bottom: 1px solid #aaa;
-		border-right: 1px solid #aaa;
-		-moz-border-radius: 0.7em;
-	}
-	
-	.status {
+        display:block;
+        margin-top:2em;
+        padding: 0 1.1em 2em;
+        background: url("rounded?bc=white&w=600&h=40&shadow=true&ah=10&aw=10&sw=4&o=.5") left bottom no-repeat;
+    }
+
+    .status {
 		display:block;
 		clear:both;
-		width:70%;
-		height: 50px;
+		width:80%;
+		height: 4em;
 		overflow:auto;
 		font-family: arial;
 		font-size:8pt;
 		padding: 0.4em;
 		border:1px dotted #D6AE33;
-	}
+        margin-top:1em;
+    }
 
     .clear {
         clear:both;
@@ -80,45 +68,37 @@
         width:100%;
         height: 20px;
     }
-
 </style>
 
-<span jwcid="localeDetail@Any">
-	<p class="detail" jwcid="@If" condition="ognl:selected" >
-		<strong>Country:</strong> 
-		<span jwcid="@Insert" value="ognl:selected.displayCountry" mode="ognl:@org.apache.tapestry.components.InsertMode@BREAK" />
-		&nbsp;
-		<strong>Language:</strong>
-		<span jwcid="@Insert" value="ognl:selected.displayLanguage" mode="ognl:@org.apache.tapestry.components.InsertMode@BREAK" />
-		&nbsp;
-		<strong>Variant:</strong>
-		<span jwcid="@Insert" value="ognl:selected.displayVariant" mode="ognl:@org.apache.tapestry.components.InsertMode@BREAK" />
-	</p>
-</span>
+<div jwcid="localeDetail@Any">
+    <p class="detail" jwcid="@If" condition="ognl:selected" >
+            <strong>Country:</strong>
+            <span jwcid="@Insert" value="ognl:selected.displayCountry" mode="ognl:@org.apache.tapestry.components.InsertMode@BREAK" />
+            &nbsp;
+            <strong>Language:</strong>
+            <span jwcid="@Insert" value="ognl:selected.displayLanguage" mode="ognl:@org.apache.tapestry.components.InsertMode@BREAK" />
+            &nbsp;
+            <strong>Variant:</strong>
+            <span jwcid="@Insert" value="ognl:selected.displayVariant" mode="ognl:@org.apache.tapestry.components.InsertMode@BREAK" />
+    </p>
+</div>
 
 <div jwcid="status@Any" class="status" >
-	<span jwcid="@Insert" value="ognl:status" />
+    <span jwcid="@Insert" value="ognl:status" />
 </div>
 
+
 <div class="ognl:currentSelected ? 'selected localeList' : 'localeList'"
      jwcid="localeList@For"
      source="ognl:@org.apache.tapestry.timetracker.page.LocaleList@LOCALES" value="ognl:currLocale">
 
-    <div jwcid="@Any" class="ognl:currentSelected ? 'selectedRoundtop' : 'roundtop'">
-        
-        <img jwcid="@Any" src="ognl:getRoundedUrl('tl')" width="8" height="8" class="corner" style="display: none" />
-        
-    </div>
-    <p>
-    <a jwcid="localeLink@DirectLink" listener="listener:selectLocale" parameters="ognl:{currLocale.language, currLocale.country, currLocale.variant}"
-       updateComponents="ognl:{'localeDetail',page.components.localeList.clientId}" stateful="ognl:false">
-        <span jwcid="@Insert" value="ognl:currLocale.toString()" />
-    </a>
-    </p>
-    <div jwcid="@Any" class="ognl:currentSelected ? 'selectedRoundbottom' : 'roundbottom'">
-
-        <img jwcid="@Any" src="ognl:getRoundedUrl('bl')" width="8" height="8" class="corner" style="display: none" />
-
+    <div jwcid="@Locale" selected="ognl:currentSelected">
+        <p>
+            <a jwcid="localeLink@DirectLink" listener="listener:selectLocale" parameters="ognl:{currLocale.language, currLocale.country, currLocale.variant}"
+               updateComponents="ognl:{'localeDetail',page.components.localeList.clientId}" stateful="ognl:false">
+                <span jwcid="@Insert" value="ognl:currLocale.toString()" />
+            </a>
+        </p>
     </div>
 </div>
 

Modified: tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/WEB-INF/timetracker.application
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/WEB-INF/timetracker.application?view=diff&rev=531707&r1=531706&r2=531707
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/WEB-INF/timetracker.application (original)
+++ tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/WEB-INF/timetracker.application Mon Apr 23 20:08:25 2007
@@ -15,15 +15,17 @@
    limitations under the License.
 -->
 
-<!DOCTYPE application PUBLIC 
-  "-//Apache Software Foundation//Tapestry Specification 4.0//EN" 
-  "http://tapestry.apache.org/dtd/Tapestry_4_0.dtd">
-	
+<!DOCTYPE application PUBLIC
+        "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
+        "http://tapestry.apache.org/dtd/Tapestry_4_0.dtd">
+
 <application name="Tapestry TimeTracker">
-   
-   <meta key="org.apache.tapestry.namespace-properties-name" value="messages" />
-   <meta key="org.apache.tapestry.page-class-packages" value="org.apache.tapestry.timetracker.page"/>
-  
-  <library id="contrib" specification-path="classpath:/org/apache/tapestry/contrib/Contrib.library"/>
-  
+
+    <meta key="org.apache.tapestry.namespace-properties-name" value="messages" />
+    <meta key="org.apache.tapestry.page-class-packages" value="org.apache.tapestry.timetracker.page"/>
+
+    <library id="contrib" specification-path="classpath:/org/apache/tapestry/contrib/Contrib.library"/>
+
+    <component-type type="Locale" specification-path="classpath:/org/apache/tapestry/timetracker/component/Locale.jwc" />
+
 </application>

Added: tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/Locale.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/Locale.java?view=auto&rev=531707
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/Locale.java (added)
+++ tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/Locale.java Mon Apr 23 20:08:25 2007
@@ -0,0 +1,48 @@
+package org.apache.tapestry.timetracker.component;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.annotations.Parameter;
+
+/**
+ *
+ */
+public abstract class Locale extends AbstractComponent {
+
+    @Parameter(required = true)
+    public abstract boolean isSelected();
+
+    public void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+    {
+        boolean selected = isSelected();
+
+        renderRow(writer, "selectedRoundtop", "roundtop", "tl", selected);
+
+        super.renderBody(writer, cycle);
+
+        renderRow(writer, "selectedRoundbottom", "roundbottom", "bl", selected);
+    }
+
+    void renderRow(IMarkupWriter writer, String selectedCssClass, String cssClass, String anchor, boolean selected)
+    {
+         writer.begin("div");
+        writer.attribute("class", selected ? selectedCssClass :cssClass);
+
+        writer.beginEmpty("img");
+        writer.attribute("src", getRoundedUrl(anchor, selected));
+        writer.attribute("width", "8");
+        writer.attribute("height", "8");
+        writer.attribute("class", "corner");
+        writer.attribute("style", "display:none");
+
+        writer.end("div");
+    }
+
+    String getRoundedUrl(String anchor, boolean selected)
+    {
+        return "rounded?c=" +
+               (selected ? "efefef" : "2A78B0")
+               + "&bc=white&w=8&h=8&a=" + anchor;
+    }
+}

Propchange: tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/Locale.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/Locale.jwc
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/Locale.jwc?view=auto&rev=531707
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/Locale.jwc (added)
+++ tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/java/org/apache/tapestry/timetracker/component/Locale.jwc Mon Apr 23 20:08:25 2007
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Copyright 2004, 2005 The Apache Software Foundation
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!DOCTYPE component-specification PUBLIC
+  "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
+  "http://tapestry.apache.org/dtd/Tapestry_4_0.dtd">
+
+<component-specification class="org.apache.tapestry.timetracker.component.Locale">
+</component-specification>