package net.xoetrope.swing;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Shape;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import javax.swing.JComponent;
import javax.swing.TransferHandler;
import javax.swing.border.Border;
import net.xoetrope.swing.dnd.XTransferHandlerFactory;
import net.xoetrope.swing.util.XGraphicsUtils;
import net.xoetrope.xui.XImageHolder;
import net.xoetrope.xui.XAttributedComponent;
import net.xoetrope.xui.XProject;
import net.xoetrope.xui.XProjectManager;
import net.xoetrope.xui.helper.XuiUtilities;
import org.jdesktop.swingx.painter.Painter;
/**
* <p>Draws an image</p>
* <p>Copyright (c) Xoetrope Ltd., 1998-2007</p>
* License: see license.txt
* $Revision: 2.18 $
*/
public class XImage extends JComponent implements XImageHolder, XAttributedComponent, MouseListener, MouseMotionListener
{
public static final int STRETCH = 0;
public static final int PRESERVE_ASPECT = 1;
public static final int NO_SCALE = 2;
public static final int TILE = 3;
protected Image image = null;
protected Image disabledImage = null;
protected String imageName;
protected BufferedImage shadow;
protected boolean drawBorder, drawShadow, fillBorder, dragEnabled, centerImage;
protected int arc;
protected int shadowSize = 10;
protected int distance = -5;
protected int stretchMode = STRETCH;
protected Painter painter;
protected MouseEvent firstMouseEvent;
/**
* The current project
*/
protected XProject currentProject;
/**
* Constructs a blank image control.
*/
public XImage()
{
currentProject = XProjectManager.getCurrentProject();
dragEnabled = true;
}
/**
* Requests a repaint of the control once it has been created
*/
public void addNotify()
{
super.addNotify();
repaint( 0 );
}
/**
* Get the size of the shadow
* @return the shadow size
*/
public int getShadowSize()
{
return shadowSize;
}
/**
* Sets the image to display.
* @param img the image
*/
public void setImage( Image img )
{
image = img;
repaint();
prepareImage( img, this );
}
/**
* Gets the name of the image being displayed.
* @return the image name
*/
public String getImageName()
{
return imageName;
}
/**
* Sets the name of the image being displayed.
* @param name the image name
*/
public void setImageName( String name )
{
imageName = name;
setImage( currentProject.getImage( imageName ));
repaint();
}
/**
* Gets the shadow flag value.
* @return true if he shadow is drawn
*/
public boolean getDrawShadow()
{
return drawShadow;
}
/**
* Gets the fill border flag value.
* @return true if the area within the border is filled
*/
public boolean getFillBorder()
{
return fillBorder;
}
/**
* Gets the arc value, the number of pixels by which the corners are radiused
* @return the arc size in pixels
*/
public int getArc()
{
return arc;
}
/**
* Sets the arc value, the number of pixels by which the corners are radiused
* @param value the arc size in pixels
*/
public void setArc( int arcSize)
{
arc = arcSize;
}
/**
* Gets the opaque flag value.
* @return true if the background is opaque
*/
public boolean getOpaque()
{
return isOpaque();
}
/**
* Gets the border flag value.
* @return true if he border is drawn
*/
public boolean getDrawBorder()
{
return drawBorder;
}
/**
* Sets the border flag value.
* @param state true if he border is drawn
*/
public void setDrawBorder( boolean state )
{
drawBorder = state;
}
/**
* Get the preferred size of this image
* @return the preferred size
*/
public Dimension getPreferredSize( )
{
Dimension dim;
if ( image == null )
dim = super.getPreferredSize();
else
dim = new Dimension( image.getWidth( null ), image.getHeight( null ));
Border border = getBorder();
if ( border != null ) {
Insets insets = border.getBorderInsets( this );
dim.setSize( dim.getWidth() + insets.left + insets.right,
dim.getHeight() + insets.top + insets.bottom );
}
return dim;
}
/**
* Renders the component
* @param g the graphics context
*/
public void paintComponent( Graphics g )
{
Dimension d = getSize();
if ( painter != null )
painter.paint( (Graphics2D)g, this, d.width, d.height );
else if ( image != null ) {
Insets insets = new Insets( 0, 0, 0, 0 );
Border border = getBorder();
if ( border != null )
insets = border.getBorderInsets( this );
Dimension componentSize = d;
Graphics2D g2d = (Graphics2D)g;
Shape borderShape = null;
int distance = 0;
int pad = ( drawBorder || fillBorder ) ? 8 : 0;
int shadowOffset = 0;
if ( drawShadow ) {
shadowOffset = shadowSize;
if ( shadow == null ) {
// Create a shadow image
BufferedImage src = new BufferedImage( d.width, d.height, fillBorder ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_INT_ARGB );
src = XuiUtilities.createCompatibleImage( g2d, src );
Graphics imgG = src.getGraphics();
if ( fillBorder ) {
// For a filled background fill the border area
Shape backgroundShape = new RoundRectangle2D.Double( insets.left,
insets.top,
componentSize.width - insets.left - insets.right,
componentSize.height - insets.top - insets.bottom,
arc, arc );
imgG.setColor( Color.gray );
((Graphics2D)imgG).fill( backgroundShape );
}
else // Otherwise generate a copy of theimage
drawImage( (Graphics2D)imgG, image, insets.left, insets.top, componentSize.width - insets.left - insets.right,
componentSize.height - insets.top - insets.bottom, null );
imgG.dispose();
// Now blue the image
shadow = new XGraphicsUtils().createShadow( src, shadowSize );
}
if ( !drawBorder )
distance = shadowSize;
d = new Dimension( componentSize.width - shadowSize - pad - pad - insets.left - insets.right,
componentSize.height - shadowSize - pad - pad - insets.top - insets.top );
if ( fillBorder ) {
// Clear the part of the background that is 'filled''
drawImage( g2d, shadow,
shadowSize, shadowSize,
componentSize.width - shadowSize,
componentSize.height - shadowSize,
null );
g.setColor( getBackground());
g.fillRoundRect( insets.left,
insets.top,
componentSize.width - shadowSize - insets.left - insets.right,
componentSize.height - shadowSize - insets.top - insets.bottom,
arc, arc );
}
else
drawImage( g2d, shadow, shadowSize + distance + pad + insets.left, shadowSize + distance + pad + insets.top, d.width, d.height, null );
}
else if ( fillBorder ) {
g2d.setColor( getBackground());
g2d.fillRoundRect( insets.left, insets.top,
componentSize.width - insets.left - insets.right,
componentSize.height - insets.top - insets.bottom, arc, arc );
}
Shape clipShape = g2d.getClip();
if ( drawBorder ) {
// Draw the border
borderShape = new RoundRectangle2D.Double( insets.left, insets.top,
componentSize.width -shadowOffset-1 - insets.left - insets.right,
componentSize.height -shadowOffset-1 - insets.top - insets.bottom,
arc, arc );
g2d.setColor( getForeground());
g2d.draw( borderShape );
Area clipArea = new Area( borderShape );
clipArea.intersect( new Area( clipShape ));
// Clip any subsequent images
g2d.setClip( clipArea );
}
if ( isEnabled())
drawImage( g2d, image, pad, pad, d.width, d.height, this );
else {
// Draw a translucent version of the image
Composite ac = g2d.getComposite();
g2d.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.5f ));
drawImage( g2d, image, pad + insets.left, pad + insets.top, d.width, d.height, this );
g2d.setComposite( ac );
}
// Restore the clip area
g2d.setClip( clipShape );
}
}
/**
* Draw the image according to the stretch mode
* @param g2d
* @param image
* @param x
* @param y
* @param w
* @param h
* @param obs
*/
public void drawImage( Graphics2D g2d, Image image, int x, int y, int w, int h, ImageObserver obs )
{
int imgW = image.getWidth( obs );
int imgH = image.getHeight( obs );
switch ( stretchMode ) {
case NO_SCALE: {
if ( centerImage ) {
int padX = ( imgW - w ) / 2;
int padY = ( imgH - h ) / 2;
g2d.drawImage( image, x + padX, y + padY, obs );
}
else
g2d.drawImage( image, x, y, obs );
}
break;
case PRESERVE_ASPECT: {
if ( imgW > imgH ) {
int destH = imgH * w / imgW;
int pad = centerImage ? ( h - destH ) / 2 : 0;
g2d.drawImage( image, x, y + pad, w, destH, obs );
}
else {
int destW = imgW * h / imgH;
int pad = centerImage ? ( w - destW ) / 2 : 0;
g2d.drawImage( image, x + pad, y, destW, imgH, obs );
}
}
break;
case TILE: {
for ( int ix = x; ix < w; ix += imgW ) {
for ( int iy = y; iy < w; iy += imgW ) {
g2d.drawImage( image, ix, iy, imgW, imgH, obs );
}
}
}
break;
case STRETCH:
default:
g2d.drawImage( image, x, y, w, h, obs );
}
}
/**
* Update the image as it is loaded.
* @param img the image
* @param infoflags the flags
* @param x the x/left coordinate
* @param y the y/top coordinate
* @param width the image width
* @param height the height
* @return super.imageUpdate(...)
*/
public boolean imageUpdate( Image img, int infoflags, int x, int y, int width, int height )
{
if ( infoflags == ImageObserver.ALLBITS ) {
repaint( x, y, getWidth(), getHeight() );
return false;
}
else if ( infoflags > ImageObserver.ALLBITS )
return false;
if ( drawShadow )
getParent().repaint();
repaint( x, y, getWidth(), getHeight() );
return true;
}
/**
* Set the background painter object
* @param xp the painter object
*/
public void setPainter( Painter xp )
{
painter = xp;
}
/**
* Get the background painter object
* @return the painter object
*/
public Painter getPainter()
{
return painter;
}
/**
* Set one or more attributes of the component.
* <OL>
* <LI>content, value=the image file name</LI>
* <LI>imagename, value=the image file name</LI>
* <LI>tooltip, value=the tooltip text</LI>
* <LI>opaque, value=true for an opaque image (fills the entire client area)</LI>
* <LI>border, value=true to draw a border around the image</LI>
* <LI>stretch, value=STRETCH = 0, PRESERVE_ASPECT = 1, NO_SCALE = 2, TILE = 3</LI>
* <LI>arc, value=the rounding arc for the border if any</LI>
* <LI>center, value=true to center the image</LI>
* </OL>
* @param attribName the attribute name
* @param attribValue the attribute value
* @return 0 for success, non zero for failure or to require some further action
*/
public int setAttribute( String attribName, Object attribValue )
{
String attribNameLwr = attribName.toLowerCase();
String attribValueStr = (String)attribValue;
String attribValueLwr = null;
if ( attribValue != null )
attribValueLwr = attribValueStr.toLowerCase();
if ( attribNameLwr.equals( "content" ) || attribNameLwr.equals( "imagename" )) {
imageName = attribValueStr;
setImage( currentProject.getImage( imageName ));
}
else if ( attribValue == null )
return -1;
else if ( attribNameLwr.equals( "tooltip" ))
setToolTipText( attribValueStr );
else if ( attribNameLwr.equals( "opaque" ))
setOpaque( attribValueLwr.equals( "true" ) );
else if ( attribNameLwr.equals( "border" ))
drawBorder = attribValueLwr.equals( "true" ) || attribValueLwr.equals( "1" );
else if ( attribNameLwr.equals( "fill" ))
fillBorder = attribValueLwr.equals( "true" ) || attribValueLwr.equals( "1" );
else if ( attribNameLwr.equals( "shadow" ))
drawShadow = attribValueLwr.equals( "true" ) || attribValueLwr.equals( "1" );
else if ( attribNameLwr.equals( "arc" ))
arc = Math.max( 0, new Integer( attribValueStr ).intValue());
else if ( attribNameLwr.equals( "stretch" ))
setStretchMode( Math.max( 0, new Integer( attribValueStr ).intValue()));
else if ( attribNameLwr.equals( "center" ))
setCenterImage( attribValueLwr.equals( "true" ));
else if ( attribNameLwr.equals( "painter" )) {
try {
Painter xp = (Painter)Class.forName( attribValueStr.trim()).newInstance();
setPainter( xp );
}
catch ( Exception e )
{}
}
else if ( attribNameLwr.equals( "dragenabled" )) {
boolean state = "true".equals( attribValueLwr );
if ( state ) {
XTransferHandlerFactory thf = XTransferHandlerFactory.getInstance( XProjectManager.getCurrentProject());
if ( thf != null ) {
TransferHandler th = thf.getTransferHandler( this, null );
if ( th != null ) {
addMouseListener( this );
addMouseMotionListener( this );
setTransferHandler( th );
return 0;
}
}
}
}
else
return -1;
return 0;
}
// DND -----------------------------------------------------------------------
public void mousePressed( MouseEvent e )
{
//Don't bother to drag if there is no image.
if ( imageName == null )
return;
firstMouseEvent = e;
e.consume();
}
public void mouseDragged( MouseEvent e )
{
//Don't bother to drag if the component displays no image.
if ( imageName == null )
return;
if ( firstMouseEvent != null ) {
e.consume();
//If they are holding down the control key, COPY rather than MOVE
int ctrlMask = InputEvent.CTRL_DOWN_MASK;
int action = (( e.getModifiersEx() & ctrlMask) == ctrlMask) ?
TransferHandler.COPY : TransferHandler.MOVE;
int dx = Math.abs( e.getX() - firstMouseEvent.getX());
int dy = Math.abs( e.getY() - firstMouseEvent.getY());
//Arbitrarily define a 5-pixel shift as the
//official beginning of a drag.
if (( dx > 5 ) || ( dy > 5 )) {
//This is a drag, not a click.
JComponent c = (JComponent)e.getSource();
//Tell the transfer handler to initiate the drag.
TransferHandler handler = c.getTransferHandler();
handler.exportAsDrag( c, firstMouseEvent, action );
firstMouseEvent = null;
}
}
}
/**
* Invoked when the mouse cursor has been moved onto a component
* but no buttons have been pushed.
*/
public void mouseMoved( MouseEvent e )
{
}
public void mouseReleased( MouseEvent e )
{
firstMouseEvent = null;
}
/**
* Invoked when the mouse enters a component.
*/
public void mouseEntered( MouseEvent e ) {}
/**
* Invoked when the mouse exits a component.
*/
public void mouseExited( MouseEvent e ) {}
/**
* Invoked when the mouse button has been clicked (pressed
* and released) on a component.
*/
public void mouseClicked( MouseEvent e ) {}
// DND -----------------------------------------------------------------------
/**
* Get the stretch mode for this image
* @return
* @since 3.2
*/
public int getStretchMode()
{
return stretchMode;
}
/**
* Set the stretch mode for this image. The mode can be
* <ul>
* <li>STRETCH = 0, the image completely fills the available area</li>
* <li>PRESERVE_ASPECT = 1, the aspect ratio is preserved while filling the maximum possible area</li>
* <li>NO_SCALE = 2, no stretching is performed and the image is drawn at it's original size</li>
* <li>TILE = 3, the image is tiles at its original size to fill the available area</li>
* </ul>
*
* @param mode the new stretch mode
* @since 3.2
*/
public void setStretchMode( int mode )
{
stretchMode = mode;
repaint();
}
/**
* Is the image cetered?
* @return true if the image is centered when the aspect is preserved or when the
* original size is smaller than the available space
*/
public boolean isCenterImage()
{
return centerImage;
}
/**
* Set the image centering options
* @param state
*/
public void setCenterImage( boolean state )
{
centerImage = state;
repaint();
}
}