home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Chip 1998 November
/
Chip_1998-11_cd.bin
/
tema
/
Cafe
/
jfc.bin
/
ImageView.java
< prev
next >
Wrap
Text File
|
1998-02-26
|
24KB
|
733 lines
/*
* @(#)ImageView.java 1.14 98/02/05
*
* Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*
*/
package com.sun.java.swing.text.html;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.ImageObserver;
import java.io.*;
import java.net.*;
import java.util.Dictionary;
import com.sun.java.swing.*;
import com.sun.java.swing.text.*;
import com.sun.java.swing.event.*;
/**
* View of an Image, intended to support the HTML <IMG> tag.
* Supports scaling via the HEIGHT and WIDTH parameters.
*
* @author Jens Alfke (based on IconView by Timothy Prinzing)
* @see IconView
*/
class ImageView extends View implements ImageObserver,
MouseListener, MouseMotionListener,
Runnable, HTMLDefs {
// --- Attribute Values ------------------------------------------
public static final String
TOP = "top",
TEXTTOP = "texttop",
MIDDLE = "middle",
ABSMIDDLE = "absmiddle",
CENTER = "center",
BOTTOM = "bottom";
// --- Construction ----------------------------------------------
/** Add an ImageData object as an attribute to the set.
This is used to tag an IMG Element with data that caches
attributes such as the Image object and dimensions.
This happens during parsing, before any views are created,
because the Element once created is immutable.
@see com.sun.java.swing.text.html.HTMLDocument#imgAction */
static void addImageDataAttribute( MutableAttributeSet attr ) {
ImageInfo img = new ImageInfo();
attr.addAttribute(ImageInfo.kAttributeName,img);
}
/**
* Creates a new view that represents an IMG element.
*
* @param elem the element to create a view for
*/
public ImageView(Element elem) {
super(elem);
// Most data is stored in an object attached to the Element:
fIMG = (ImageInfo) elem.getAttributes().getAttribute(ImageInfo.kAttributeName);
fIMG.initialize(this,elem);
fHeight = fIMG.fHeight;
fWidth = fIMG.fWidth;
}
/**
* Establishes the parent view for this view.
* Seize this moment to cache the AWT Container I'm in.
*/
public void setParent(View parent) {
super.setParent(parent);
fContainer = parent!=null ?getContainer() :null;
if( parent==null && fComponent!=null ) {
fComponent.getParent().remove(fComponent);
fComponent = null;
}
}
/** My attributes may have changed. */
public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
//$ Beware: This method has never been tested, since ParagraphView does
// not propagate changedUpdates to its child Views (bug?) --jpa 1/7/98
if(DEBUG) System.out.println("ImageView: changedUpdate begin...");
super.changedUpdate(e,a,f);
float align = fIMG.getVerticalAlignment();
fIMG.changedUpdate(this,getElement());
boolean hChanged=false, wChanged=false;
if( fHeight!=fIMG.fHeight ) {
fHeight = fIMG.fHeight;
hChanged = true;
}
if( fWidth!=fIMG.fWidth ) {
fWidth = fIMG.fWidth;
wChanged = true;
}
if( hChanged || wChanged || fIMG.getVerticalAlignment()!=align ) {
if(DEBUG) System.out.println("ImageView: calling preferenceChanged");
getParent().preferenceChanged(this,hChanged,wChanged);
}
if(DEBUG) System.out.println("ImageView: changedUpdate end; valign="+fIMG.getVerticalAlignment());
}
// --- Painting --------------------------------------------------------
/**
* Paints the image.
*
* @param g the rendering surface to use
* @param a the allocated region to render into
* @see View#paint
*/
public void paint(Graphics g, Shape a) {
Color oldColor = g.getColor();
fBounds = a.getBounds();
int border = fIMG.getBorder();
int x = fBounds.x + border + fIMG.getSpace(X_AXIS);
int y = fBounds.y + border + fIMG.getSpace(Y_AXIS);
int width = fIMG.fWidth;
int height = fIMG.fHeight;
int sel = getSelectionState();
// Make sure my Component is in the right place:
if( fComponent == null ) {
fComponent = new Component() { };
fComponent.addMouseListener(this);
fComponent.addMouseMotionListener(this);
fComponent.setCursor(Cursor.getDefaultCursor()); // use arrow cursor
fContainer.add(fComponent);
}
fComponent.setBounds(x,y,width,height);
// If no pixels yet, draw gray outline and icon:
if( ! fIMG.hasPixels(this) ) {
g.setColor(Color.lightGray);
g.drawRect(x,y,width-1,height-1);
g.setColor(oldColor);
loadIcons();
Icon icon = fIMG.fImage==null ?sMissingImageIcon :sPendingImageIcon;
if( icon != null )
icon.paintIcon(getContainer(), g, x, y);
}
// Draw image:
if( fIMG.fImage != null ) {
// Use Xor mode when selected/highlighted.
//! Could darken image instead, but it would be more expensive.
if( sel > 0 )
g.setXORMode(Color.white);
g.drawImage(fIMG.fImage,x, y,
width,height,this);
if( sel > 0 )
g.setPaintMode();
}
// If selected exactly, we need a black border & grow-box:
Color bc = fIMG.getBorderColor();
if( sel == 2 ) {
// Make sure there's room for a border:
int delta = 2-border;
if( delta > 0 ) {
x += delta;
y += delta;
width -= delta<<1;
height -= delta<<1;
border = 2;
}
bc = null;
g.setColor(Color.black);
// Draw grow box:
g.fillRect(x+width-5,y+height-5,5,5);
}
// Draw border:
if( border > 0 ) {
if( bc != null ) g.setColor(bc);
// Draw a thick rectangle:
for( int i=1; i<=border; i++ )
g.drawRect(x-i, y-i, width-1+i+i, height-1+i+i);
g.setColor(oldColor);
}
}
/** Request that this view be repainted.
Assumes the view is still at its last-drawn location. */
protected void repaint( long delay ) {
if( fContainer != null && fBounds!=null ) {
fContainer.repaint(delay,
fBounds.x,fBounds.y,fBounds.width,fBounds.height);
}
}
/** Determines whether the image is selected, and if it's the only thing selected.
@return 0 if not selected, 1 if selected, 2 if exclusively selected. */
protected int getSelectionState( ) {
int p0 = fIMG.fElement.getStartOffset();
int p1 = fIMG.fElement.getEndOffset();
JTextComponent textComp = (JTextComponent)fContainer;
Highlighter highlighter = textComp.getHighlighter();
Highlighter.Highlight[] hi = highlighter.getHighlights();
for( int i=hi.length-1; i>=0; i-- ) {
Highlighter.Highlight h = hi[i];
int start = h.getStartOffset();
int end = h.getEndOffset();
if( start<=p0 && end>=p1 ) {
if( start==p0 && end==p1 )
return 2;
else
return 1;
}
}
return 0;
}
/** Returns the text editor's highlight color. */
protected Color getHighlightColor( ) {
JTextComponent textComp = (JTextComponent)fContainer;
return textComp.getSelectionColor();
}
// --- Progressive display ---------------------------------------------
public boolean imageUpdate( Image img, int flags, int x, int y,
int width, int height ) {
if( fIMG.fImage==null )
return false;
// Bail out if there was an error:
if( (flags & (ABORT|ERROR)) != 0 ) {
fIMG.fImage = null;
repaint(0);
return false;
}
// Resize image if necessary:
if( (flags & ImageObserver.HEIGHT) != 0 )
if( ! getElement().getAttributes().isDefined(HTMLDefs.HEIGHT) )
fIMG.fHeight = height;
if( (flags & ImageObserver.WIDTH) != 0 )
if( ! getElement().getAttributes().isDefined(HTMLDefs.WIDTH) )
fIMG.fWidth = width;
if( fHeight != fIMG.fHeight || fWidth != fIMG.fWidth ) {
// May need to resize myself, asynchronously:
fHeight = fIMG.fHeight;
fWidth = fIMG.fWidth;
if( DEBUG ) System.out.println("ImageView: resized to "+fWidth+"x"+fHeight);
SwingUtilities.invokeLater(this); // call run() later
return true;
}
// Repaint when done or when new pixels arrive:
if( (flags & (FRAMEBITS|ALLBITS)) != 0 )
repaint(0);
else if( (flags & SOMEBITS) != 0 )
if( sIsInc )
repaint(sIncRate);
return true;
}
public void run( ) {
if(DEBUG)System.out.println("ImageView: Called preferenceChanged");
preferenceChanged(this,true,true);
}
/**
* Static properties for incremental drawing.
* Swiped from Component.java
* @see #imageUpdate
*/
private static boolean sIsInc;
private static int sIncRate;
static {
String s;
s = System.getProperty("awt.image.incrementaldraw");
sIsInc = (s == null || s.equals("true"));
s = System.getProperty("awt.image.redrawrate");
sIncRate = (s != null) ? Integer.parseInt(s) : 100;
}
// --- Layout ----------------------------------------------------------
/**
* Determines the preferred span for this view along an
* axis.
*
* @param axis may be either X_AXIS or Y_AXIS
* @returns the span the view would like to be rendered into.
* Typically the view is told to render into the span
* that is returned, although there is no guarantee.
* The parent may choose to resize or break the view.
*/
public float getPreferredSpan(int axis) {
//if(DEBUG)System.out.println("ImageView: getPreferredSpan");
int extra = 2*(fIMG.getBorder()+fIMG.getSpace(axis));
switch (axis) {
case View.X_AXIS:
return fIMG.fWidth+extra;
case View.Y_AXIS:
return fIMG.fHeight+extra;
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
/**
* Determines the desired alignment for this view along an
* axis. This is implemented to give the alignment to the
* bottom of the icon along the y axis, and the default
* along the x axis.
*
* @param axis may be either X_AXIS or Y_AXIS
* @returns the desired alignment. This should be a value
* between 0.0 and 1.0 where 0 indicates alignment at the
* origin and 1.0 indicates alignment to the full span
* away from the origin. An alignment of 0.5 would be the
* center of the view.
*/
public float getAlignment(int axis) {
switch (axis) {
case View.Y_AXIS:
return fIMG.getVerticalAlignment();
default:
return super.getAlignment(axis);
}
}
/**
* Provides a mapping from the document model coordinate space
* to the coordinate space of the view mapped to it.
*
* @param pos the position to convert
* @param a the allocated region to render into
* @return the bounding box of the given position
* @exception BadLocationException if the given position does not represent a
* valid location in the associated document
* @see View#modelToView
*/
public Shape modelToView(int pos, Shape a) throws BadLocationException {
int p0 = getStartOffset();
int p1 = getEndOffset();
if ((pos >= p0) && (pos < p1)) {
Rectangle r = new Rectangle(a.getBounds());
r.width = 0;
return r;
}
return null;
}
/**
* Provides a mapping from the view coordinate space to the logical
* coordinate space of the model.
*
* @param x the X coordinate
* @param y the Y coordinate
* @param a the allocated region to render into
* @return the location within the model that best represents the
* given point of view
* @see View#viewToModel
*/
public int viewToModel(float x, float y, Shape a) {
return getStartOffset();
}
/**
* Set the size of the view. (Ignored.)
*
* @param width the width
* @param height the height
*/
public void setSize(float width, float height) {
// Ignore this -- image size is determined by the tag attrs and
// the image itself, not the surrounding layout!
}
/** Change the size of this image. This alters the HEIGHT and WIDTH
attributes of the Element and causes a re-layout. */
protected void resize( int width, int height ) {
if( width==fIMG.fWidth && height==fIMG.fHeight )
return;
//$ Should not be necessary if changedUpdate were called,
// but currently (1/8/98) it isn't, so...
fWidth = fIMG.fWidth = width;
fHeight= fIMG.fHeight= height;
// Replace attributes in document:
MutableAttributeSet attr = new SimpleAttributeSet();
attr.addAttribute(HTMLDefs.WIDTH ,Integer.toString(width));
attr.addAttribute(HTMLDefs.HEIGHT,Integer.toString(height));
((StyledDocument)getDocument()).setCharacterAttributes(
fIMG.fElement.getStartOffset(),
fIMG.fElement.getEndOffset(),
attr, false);
}
// --- Mouse event handling --------------------------------------------
/** Select or grow image when clicked. */
public void mousePressed(MouseEvent e){
Dimension size = fComponent.getSize();
if( e.getX() >= size.width-7 && e.getY() >= size.height-7
&& getSelectionState()==2 ) {
// Click in selected grow-box:
if(DEBUG)System.out.println("ImageView: grow!!! Size="+fWidth+"x"+fHeight);
Point loc = fComponent.getLocationOnScreen();
fGrowBase = new Point(loc.x+e.getX() - fWidth,
loc.y+e.getY() - fHeight);
fGrowProportionally = e.isShiftDown();
} else {
// Else select image:
fGrowBase = null;
JTextComponent comp = (JTextComponent)fContainer;
int start = fIMG.fElement.getStartOffset();
int end = fIMG.fElement.getEndOffset();
int mark = comp.getCaret().getMark();
int dot = comp.getCaret().getDot();
if( e.isShiftDown() ) {
// extend selection if shift key down:
if( mark <= start )
comp.moveCaretPosition(end);
else
comp.moveCaretPosition(start);
} else {
// just select image, without shift:
if( mark!=start )
comp.setCaretPosition(start);
if( dot!=end )
comp.moveCaretPosition(end);
}
}
}
/** Resize image if initial click was in grow-box: */
public void mouseDragged(MouseEvent e ) {
if( fGrowBase != null ) {
Point loc = fComponent.getLocationOnScreen();
int width = Math.max(2, loc.x+e.getX() - fGrowBase.x);
int height= Math.max(2, loc.y+e.getY() - fGrowBase.y);
if( e.isShiftDown() && fIMG.fImage!=null ) {
// Make sure size is proportional to actual image size:
int imgWidth = fIMG.fImage.getWidth(this);
int imgHeight = fIMG.fImage.getHeight(this);
if( imgWidth>0 && imgHeight>0 ) {
float prop = (float)imgHeight / (float)imgWidth;
float pwidth = height / prop;
float pheight= width * prop;
if( pwidth > width )
width = (int) pwidth;
else
height = (int) pheight;
}
}
resize(width,height);
}
}
public void mouseReleased(MouseEvent e){
fGrowBase = null;
//! Should post some command to make the action undo-able
}
/** On double-click, open image properties dialog. */
public void mouseClicked(MouseEvent e){
if( e.getClickCount() == 2 ) {
System.out.println("ImageView: Double-click!"); //$ IMPLEMENT
}
}
public void mouseEntered(MouseEvent e){
}
public void mouseMoved(MouseEvent e ) {
}
public void mouseExited(MouseEvent e){
}
// --- Static icon accessors -------------------------------------------
private Icon makeIcon(final String gifFile) throws IOException {
/* Copy resource into a byte array. This is
* necessary because several browsers consider
* Class.getResource a security risk because it
* can be used to load additional classes.
* Class.getResourceAsStream just returns raw
* bytes, which we can convert to an image.
*/
InputStream resource =
ImageView.class.getResourceAsStream(gifFile);
if (resource == null) {
System.err.println(ImageView.class.getName() + "/" +
gifFile + " not found.");
return null;
}
BufferedInputStream in =
new BufferedInputStream(resource);
ByteArrayOutputStream out =
new ByteArrayOutputStream(1024);
byte[] buffer = new byte[1024];
int n;
while ((n = in.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
in.close();
out.flush();
buffer = out.toByteArray();
if (buffer.length == 0) {
System.err.println("warning: " + gifFile +
" is zero-length");
return null;
}
return new ImageIcon(buffer);
}
private void loadIcons( ) {
try{
if( sPendingImageIcon == null )
sPendingImageIcon = makeIcon(PENDING_IMAGE_SRC);
if( sMissingImageIcon == null )
sMissingImageIcon = makeIcon(MISSING_IMAGE_SRC);
}catch( Exception x ) {
System.err.println("ImageView: Couldn't load image icons");
}
}
// --- member variables ------------------------------------------------
private ImageInfo fIMG;
private int fHeight, fWidth;
private Container fContainer;
private Rectangle fBounds;
private Component fComponent;
private Point fGrowBase; // base of drag while growing image
private boolean fGrowProportionally; // should grow be proportional?
// --- constants and static stuff --------------------------------
private static Icon sPendingImageIcon,
sMissingImageIcon;
private static final String
PENDING_IMAGE_SRC = "icons/image-delayed.gif", // both stolen from HotJava
MISSING_IMAGE_SRC = "icons/image-failed.gif";
private static final boolean DEBUG = false;
/** Shared data for Elements on IMG tags. (Inner class!)
*/
static class ImageInfo implements HTMLDefs {
ImageInfo( ) {
}
void initialize( ImageView view, Element elem ) {
if( fElement != null )
return;
fElement = elem;
// Request image from document's cache:
AttributeSet attr = elem.getAttributes();
URL src = getSourceURL();
if( src != null ) {
Dictionary cache = (Dictionary) view.getDocument().getProperty(IMAGE_CACHE_PROPERTY);
if( cache != null )
fImage = (Image) cache.get(src);
else
fImage = Toolkit.getDefaultToolkit().getImage(src);
}
// Get height/width from params or image or defaults:
fHeight = getIntAttr(HTMLDefs.HEIGHT,-1);
boolean customHeight = (fHeight>0);
if( !customHeight && fImage != null )
fHeight = fImage.getHeight(view);
if( fHeight <= 0 )
fHeight = DEFAULT_HEIGHT;
fWidth = getIntAttr(HTMLDefs.WIDTH,-1);
boolean customWidth = (fWidth>0);
if( !customWidth && fImage != null )
fWidth = fImage.getWidth(view);
if( fWidth <= 0 )
fWidth = DEFAULT_WIDTH;
// Make sure the image starts loading:
if( fImage != null )
if( customWidth && customHeight )
Toolkit.getDefaultToolkit().prepareImage(fImage,fHeight,fWidth,view);
else
Toolkit.getDefaultToolkit().prepareImage(fImage,-1,-1,view);
if( DEBUG ) {
if( fImage != null )
System.out.println("ImageInfo: new on "+src+" ("+fWidth+"x"+fHeight+")");
else
System.out.println("ImageInfo: couldn't get image at "+src);
if(isLink()) System.out.println(" It's a link! Border = "+getBorder());
//((AbstractDocument.AbstractElement)elem).dump(System.out,4);
}
}
/** Attributes may have changed, so re-read them. */
void changedUpdate( ImageView view, Element elem ) {
fElement = null;
initialize(view,elem);
}
/** Is this image within a link? */
boolean isLink( ) {
//! It would be nice to cache this but in an editor it can change
// See if I have an HREF attribute courtesy of the enclosing A tag:
return fElement.getAttributes().isDefined(HREF);
}
/** Returns the size of the border to use. */
int getBorder( ) {
return getIntAttr(BORDER, isLink() ?DEFAULT_BORDER :0);
}
/** Returns the amount of extra space to add along an axis. */
int getSpace( int axis ) {
return getIntAttr( axis==X_AXIS ?HSPACE :VSPACE,
0 );
}
/** Returns the border's color, or null if this is not a link. */
Color getBorderColor( ) {
// Basically stolen from LabelView.sync:
HTMLDocument doc = (HTMLDocument) fElement.getDocument();
AttributeSet attr = fElement.getAttributes();
return doc.getForeground(attr);
}
/** Returns the image's vertical alignment. */
float getVerticalAlignment( ) {
String align = (String) fElement.getAttributes().getAttribute(ALIGN);
if( align != null ) {
align = align.toLowerCase();
if( align.equals(TOP) || align.equals(TEXTTOP) )
return 0.0f;
else if( align.equals(this.CENTER) || align.equals(MIDDLE)
|| align.equals(ABSMIDDLE) )
return 0.5f;
}
return 1.0f; // default alignment is bottom
}
boolean hasPixels( ImageObserver obs ) {
return fImage != null && fImage.getHeight(obs)>0
&& fImage.getWidth(obs)>0;
}
/** Return a URL for the image source,
or null if it could not be determined. */
private URL getSourceURL( ) {
String src = (String) fElement.getAttributes().getAttribute(SRC);
if( src==null ) return null;
URL reference = (URL) fElement.getDocument().getProperty(Document.StreamDescriptionProperty);
try {
return new URL(reference,src);
} catch (MalformedURLException e) {
return null;
}
}
/** Look up an integer-valued attribute. <b>Not</b> recursive. */
private int getIntAttr( String name, int deflt ) {
AttributeSet attr = fElement.getAttributes();
if( attr.isDefined(name) ) { // does not check parents!
int i;
String val = (String) attr.getAttribute(name);
if( val == null )
i = deflt;
else
try{
i = Math.max(0, Integer.parseInt(val));
}catch( NumberFormatException x ) {
i = deflt;
}
return i;
} else
return deflt;
}
Element fElement;
Image fImage;
int fHeight,fWidth;
static final String kAttributeName = "$IMGObject";
//$ move this someplace public
static final String IMAGE_CACHE_PROPERTY = "imageCache";
// Height/width to use before we know the real size:
private static final int
DEFAULT_WIDTH = 32,
DEFAULT_HEIGHT= 32,
// Default value of BORDER param: //? possibly move into stylesheet?
DEFAULT_BORDER= 2;
} // end of ImageView.ImageInfo
} // end of ImageView