
com.viaoa.jfc.editor.html.view.MyImageView Maven / Gradle / Ivy
/*
* @(#)ImageView.java 1.58 05/11/30
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.viaoa.jfc.editor.html.view;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.ImageObserver;
import java.io.*;
import java.net.*;
import java.util.Dictionary;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.CSS;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.InlineView;
import javax.swing.text.html.StyleSheet;
import javax.swing.event.*;
import com.viaoa.jfc.image.OAImageUtil;
import com.viaoa.util.OAConv;
/*** vv
* Copied from ImageView
*
* Goals for changes:
*
* - allow for loading using File name/path
*
- allow for scale size when printing images that are larger then page width/height
*
*/
/**
* View of an Image, intended to support the HTML <IMG> tag. Supports
* scaling via the HEIGHT and WIDTH attributes of the tag. If the image is
* unable to be loaded any text specified via the ALT
attribute
* will be rendered.
*
* While this class has been part of swing for a while now, it is public as of
* 1.4.
*
* @author Scott Violet
* @version 1.58 11/30/05
* @see IconView
* @since 1.4
*/
public abstract class MyImageView extends View {
// vv
public float scale;
/**
* If true, when some of the bits are available a repaint is done.
*
* This is set to false as swing does not offer a repaint that takes a
* delay. If this were true, a bunch of immediate repaints would get
* generated that end up significantly delaying the loading of the image (or
* anything else going on for that matter).
*/
private static boolean sIsInc = false;
/**
* Repaint delay when some of the bits are available.
*/
private static int sIncRate = 100;
/**
* Property name for pending image icon
*/
private static final String PENDING_IMAGE = "html.pendingImage";
/**
* Property name for missing image icon
*/
private static final String MISSING_IMAGE = "html.missingImage";
/**
* Document property for image cache.
*/
private static final String IMAGE_CACHE_PROPERTY = "imageCache";
// Height/width to use before we know the real size, these should at least
// the size of sMissingImageIcon
and
// sPendingImageIcon
private static final int DEFAULT_WIDTH = 38;
private static final int DEFAULT_HEIGHT = 38;
/**
* Default border to use if one is not specified.
*/
private static final int DEFAULT_BORDER = 2;
// Bitmask values
private static final int LOADING_FLAG = 1;
private static final int LINK_FLAG = 2;
private static final int WIDTH_FLAG = 4;
private static final int HEIGHT_FLAG = 8;
private static final int RELOAD_FLAG = 16;
private static final int RELOAD_IMAGE_FLAG = 32;
private static final int SYNC_LOAD_FLAG = 64;
private AttributeSet attr;
private Image image;
private int width;
private int height;
/**
* Bitmask containing some of the above bitmask values. Because the image
* loading notification can happen on another thread access to this is
* synchronized (at least for modifying it).
*/
private int state;
private Container container;
private Rectangle fBounds;
private Color borderColor;
// Size of the border, the insets contains this valid. For example, if
// the HSPACE attribute was 4 and BORDER 2, leftInset would be 6.
private short borderSize;
// Insets, obtained from the painter.
private short leftInset;
private short rightInset;
private short topInset;
private short bottomInset;
/**
* We don't directly implement ImageObserver, instead we use an instance
* that calls back to us.
*/
private ImageObserver imageObserver;
/**
* Used for alt text. Will be non-null if the image couldn't be found, and
* there is valid alt text.
*/
private View altView;
/** Alignment along the vertical (Y) axis. */
private float vAlign;
//vv
protected PrintBoxView getPrintBoxView(View view) {
if (view == null || view instanceof PrintBoxView) return (PrintBoxView) view;
return getPrintBoxView(view.getParent());
}
/**
* Creates a new view that represents an IMG element.
*
* @param elem
* the element to create a view for
*/
public MyImageView(Element elem) {
super(elem);
// vv
setLoadsSynchronously(true);
fBounds = new Rectangle();
imageObserver = new ImageHandler();
state = RELOAD_FLAG | RELOAD_IMAGE_FLAG;
}
/**
* Returns the text to display if the image can't be loaded. This is
* obtained from the Elements attribute set with the attribute name
* HTML.Attribute.ALT
.
*/
public String getAltText() {
return (String) getElement().getAttributes().getAttribute(
HTML.Attribute.ALT);
}
// vv
public URL getImageURL() {
String src = (String)getElement().getAttributes().getAttribute(HTML.Attribute.SRC);
if (src == null) {
return null;
}
try {
if (!src.toLowerCase().startsWith("file:/")) {
File file = new File(src);
if (file.exists()) {
return file.toURI().toURL();
}
}
URL url = new URL(src);
return url;
}
catch (Exception e) {
int xx = 4;
}
return getImageURL_Orig();
}
/**
* Return a URL for the image source, or null if it could not be determined.
*/
public URL getImageURL_Orig() {
String src = (String) getElement().getAttributes().getAttribute(
HTML.Attribute.SRC);
if (src == null) {
return null;
}
URL reference = ((HTMLDocument) getDocument()).getBase();
try {
URL u = new URL(reference, src);
return u;
} catch (MalformedURLException e) {
return null;
}
}
/**
* Returns the icon to use if the image couldn't be found.
*/
public Icon getNoImageIcon() {
return (Icon) UIManager.getLookAndFeelDefaults().get(MISSING_IMAGE);
}
/**
* Returns the icon to use while in the process of loading the image.
*/
public Icon getLoadingImageIcon() {
return (Icon) UIManager.getLookAndFeelDefaults().get(PENDING_IMAGE);
}
/**
* Returns the image to render.
*/
public Image getImage() {
sync();
return image;
}
// vv Called by OAHTMLTextPaneController.onEditImage()
public void setImage(Image img) {
if (img != null) OAImageUtil.loadImage(img);
this.image = img;
width = image == null ? 0 : image.getWidth(null);
height = image == null ? 0 : img.getHeight(null);
scale = 0f;
Dictionary cache = (Dictionary) getDocument().getProperty(IMAGE_CACHE_PROPERTY);
if (cache != null) {
URL src = getImageURL();
if (src != null) {
if (img != null) cache.put(src, img);
else cache.remove(src);
}
}
View parent = getParent();
if (parent != null) {
parent.preferenceChanged(this, true, true);
}
}
/**
* Sets how the image is loaded. If newValue
is true, the image
* we be loaded when first asked for, otherwise it will be loaded
* asynchronously. The default is to not load synchronously, that is to load
* the image asynchronously.
*/
public void setLoadsSynchronously(boolean newValue) {
synchronized (this) {
if (newValue) {
state |= SYNC_LOAD_FLAG;
} else {
state = (state | SYNC_LOAD_FLAG) ^ SYNC_LOAD_FLAG;
}
}
}
/**
* Returns true if the image should be loaded when first asked for.
*/
public boolean getLoadsSynchronously() {
return ((state & SYNC_LOAD_FLAG) != 0);
}
/**
* Convenience method to get the StyleSheet.
*/
protected StyleSheet getStyleSheet() {
HTMLDocument doc = (HTMLDocument) getDocument();
return doc.getStyleSheet();
}
/**
* Fetches the attributes to use when rendering. This is implemented to
* multiplex the attributes specified in the model with a StyleSheet.
*/
public AttributeSet getAttributes() {
sync();
return attr;
}
/**
* For images the tooltip text comes from text specified with the
* ALT
attribute. This is overriden to return
* getAltText
.
*
* @see JTextComponent#getToolTipText
*/
public String getToolTipText(float x, float y, Shape allocation) {
return getAltText();
}
/**
* Update any cached values that come from attributes.
*/
protected void setPropertiesFromAttributes() {
StyleSheet sheet = getStyleSheet();
this.attr = sheet.getViewAttributes(this);
// Gutters
borderSize = (short) getIntAttr(HTML.Attribute.BORDER,
isLink() ? DEFAULT_BORDER : 0);
leftInset = rightInset = (short) (getIntAttr(HTML.Attribute.HSPACE, 0) + borderSize);
topInset = bottomInset = (short) (getIntAttr(HTML.Attribute.VSPACE, 0) + borderSize);
borderColor = ((StyledDocument) getDocument())
.getForeground(getAttributes());
AttributeSet attr = getElement().getAttributes();
// Alignment.
// PENDING: This needs to be changed to support the CSS versions
// when conversion from ALIGN to VERTICAL_ALIGN is complete.
Object alignment = attr.getAttribute(HTML.Attribute.ALIGN);
vAlign = 1.0f;
if (alignment != null) {
alignment = alignment.toString();
if ("top".equals(alignment)) {
vAlign = 0f;
} else if ("middle".equals(alignment)) {
vAlign = .5f;
}
}
AttributeSet anchorAttr = (AttributeSet) attr.getAttribute(HTML.Tag.A);
if (anchorAttr != null && anchorAttr.isDefined(HTML.Attribute.HREF)) {
synchronized (this) {
state |= LINK_FLAG;
}
} else {
synchronized (this) {
state = (state | LINK_FLAG) ^ LINK_FLAG;
}
}
}
/**
* Establishes the parent view for this view. Seize this moment to cache the
* AWT Container I'm in.
*/
public void setParent(View parent) {
View oldParent = getParent();
super.setParent(parent);
container = (parent != null) ? getContainer() : null;
if (oldParent != parent) {
synchronized (this) {
state |= RELOAD_FLAG;
}
}
}
/**
* Invoked when the Elements attributes have changed. Recreates the image.
*/
public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
super.changedUpdate(e, a, f);
synchronized (this) {
state |= RELOAD_FLAG | RELOAD_IMAGE_FLAG;
}
// Assume the worst.
preferenceChanged(null, true, true);
}
/**
* Paints the View.
*
* @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) {
sync();
Rectangle rect = (a instanceof Rectangle) ? (Rectangle) a : a
.getBounds();
getImage();
Rectangle clip = g.getClipBounds();
fBounds.setBounds(rect);
paintHighlights(g, a);
paintBorder(g, rect);
if (clip != null) {
g.clipRect(rect.x + leftInset, rect.y + topInset, rect.width
- leftInset - rightInset, rect.height - topInset
- bottomInset);
}
if (image != null) {
if (!hasPixels(image)) {
// No pixels yet, use the default
Icon icon = (image == null) ? getNoImageIcon()
: getLoadingImageIcon();
if (icon != null) {
icon.paintIcon(getContainer(), g, rect.x + leftInset,
rect.y + topInset);
}
} else {
// Draw the image
getScale(); //vv: make sure that it is refreshed
g.drawImage(image, rect.x + leftInset, rect.y + topInset,(int)(width*scale), (int)(height*scale), imageObserver);
/*qqqqqqqqqqqqqqqqqqq
Graphics2D gd = (Graphics2D) g;
gd.setColor(Color.blue);//qqqqqqqqqqqqqqqq
gd.setStroke(new BasicStroke(3.0f));
//gd.fillRect(rect.x + leftInset, rect.y + topInset,(int)(width), (int)(height));
gd.setColor(Color.green);//qqqqqqqqqqqqqqqq
gd.drawLine(rect.x,(int)(rect.y + topInset+height)-3,1550,(int)(rect.y + topInset+height)-3);
*/
}
} else {
Icon icon = getNoImageIcon();
if (icon != null) {
icon.paintIcon(getContainer(), g, rect.x + leftInset, rect.y
+ topInset);
}
View view = getAltView();
// Paint the view representing the alt text, if its non-null
if (view != null
&& ((state & WIDTH_FLAG) == 0 || width > DEFAULT_WIDTH)) {
// Assume layout along the y direction
Rectangle altRect = new Rectangle(rect.x + leftInset
+ DEFAULT_WIDTH, rect.y + topInset, rect.width
- leftInset - rightInset - DEFAULT_WIDTH, rect.height
- topInset - bottomInset);
view.paint(g, altRect);
}
}
if (clip != null) {
// Reset clip.
g.setClip(clip.x, clip.y, clip.width, clip.height);
}
}
private void paintHighlights(Graphics g, Shape shape) {
if (container instanceof JTextComponent) {
JTextComponent tc = (JTextComponent) container;
Highlighter h = tc.getHighlighter();
if (h instanceof LayeredHighlighter) {
((LayeredHighlighter) h).paintLayeredHighlights(g,
getStartOffset(), getEndOffset(), shape, tc, this);
}
}
}
private void paintBorder(Graphics g, Rectangle rect) {
Color color = borderColor;
if ((borderSize > 0 || image == null) && color != null) {
int xOffset = leftInset - borderSize;
int yOffset = topInset - borderSize;
g.setColor(color);
int n = (image == null) ? 1 : borderSize;
for (int counter = 0; counter < n; counter++) {
g.drawRect(rect.x + xOffset + counter, rect.y + yOffset
+ counter, rect.width - counter - counter - xOffset
- xOffset - 1, rect.height - counter - counter
- yOffset - yOffset - 1);
}
}
}
//vv
protected float getScale() {
if (scale != 0.0) return scale;
if (image == null) return 1.0f;
scale = 1.0f;
try {
String s;
// the height/width attributes will be in CSS
Object objx = getElement().getAttributes().getAttribute(CSS.Attribute.WIDTH);
if (objx != null) {
s = objx.toString();
int pos = s.indexOf('%');
if (pos > 0) s = s.substring(0,pos);
s = s.trim();
int x = OAConv.toInt(s);
if (x > 0) {
if (pos > 0) scale = Math.min(scale, x / 100.0f);
else scale = Math.min(scale, (float)x/(float)width);
}
}
objx = getElement().getAttributes().getAttribute(CSS.Attribute.HEIGHT);
if (objx != null) {
s = objx.toString();
int pos = s.indexOf('%');
if (pos > 0) s = s.substring(0,pos);
s = s.trim();
int x = OAConv.toInt(s);
if (x > 0) {
if (pos > 0) scale = Math.min(scale, x / 100.0f);
else scale = Math.min(scale, (float)x/(float)height);
}
}
}
catch (Throwable t) {
System.out.println("MyImageView.getScale exception:"+t+", will use 1.0");//qqqqqqq
scale = 1.0f;
}
int w, h;
PrintBoxView pbView = getPrintBoxView(this);
if (pbView != null) {
w = pbView.getPageWidth();
h = pbView.getPageHeight();
}
else w = h = 0;
if (w > 0.0f) scale = Math.min(scale, (float)w/(float)width);
if (h > 0.0f) scale = Math.min(scale, (float)h/(float)height);
scale = Math.min(scale, 1.0f);
return scale;
}
/**
* Determines the preferred span for this view along an axis.
*
* @param axis
* may be either X_AXIS or Y_AXIS
* @return 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) {
sync();
getScale(); //vv: make sure that it is refreshed
// If the attributes specified a width/height, always use it!
if (axis == View.X_AXIS && (state & WIDTH_FLAG) == WIDTH_FLAG) {
getPreferredSpanFromAltView(axis);
return (width + leftInset + rightInset) * scale;
}
if (axis == View.Y_AXIS && (state & HEIGHT_FLAG) == HEIGHT_FLAG) {
getPreferredSpanFromAltView(axis);
return (height + topInset + bottomInset) * scale;
}
getImage();
if (image != null) {
switch (axis) {
case View.X_AXIS:
return (width + leftInset + rightInset) * scale;
case View.Y_AXIS:
return (height + topInset + bottomInset) * scale;
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
} else {
View view = getAltView();
float retValue = 0f;
if (view != null) {
retValue = view.getPreferredSpan(axis);
}
switch (axis) {
case View.X_AXIS:
return retValue + (float) (width + leftInset + rightInset);
case View.Y_AXIS:
return retValue + (float) (height + topInset + bottomInset);
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
* @return 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 vAlign;
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, Position.Bias b)
throws BadLocationException {
int p0 = getStartOffset();
int p1 = getEndOffset();
if ((pos >= p0) && (pos <= p1)) {
Rectangle r = a.getBounds();
if (pos == p1) {
r.x += r.width;
}
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, Position.Bias[] bias) {
Rectangle alloc = (Rectangle) a;
if (x < alloc.x + alloc.width) {
bias[0] = Position.Bias.Forward;
return getStartOffset();
}
bias[0] = Position.Bias.Backward;
return getEndOffset();
}
/**
* Sets the size of the view. This should cause layout of the view if it has
* any layout duties.
*
* @param width
* the width >= 0
* @param height
* the height >= 0
*/
public void setSize(float width, float height) {
sync();
if (getImage() == null) {
View view = getAltView();
if (view != null) {
view
.setSize(
Math
.max(
0f,
width
- (float) (DEFAULT_WIDTH
+ leftInset + rightInset)),
Math.max(0f, height
- (float) (topInset + bottomInset)));
}
}
}
/**
* Returns true if this image within a link?
*/
private boolean isLink() {
return ((state & LINK_FLAG) == LINK_FLAG);
}
/**
* Returns true if the passed in image has a non-zero width and height.
*/
private boolean hasPixels(Image image) {
return image != null && (image.getHeight(imageObserver) > 0)
&& (image.getWidth(imageObserver) > 0);
}
/**
* Returns the preferred span of the View used to display the alt text, or 0
* if the view does not exist.
*/
private float getPreferredSpanFromAltView(int axis) {
if (getImage() == null) {
View view = getAltView();
if (view != null) {
return view.getPreferredSpan(axis);
}
}
return 0f;
}
/**
* Request that this view be repainted. Assumes the view is still at its
* last-drawn location.
*/
private void repaint(long delay) {
if (container != null && fBounds != null) {
container.repaint(delay, fBounds.x, fBounds.y, fBounds.width,
fBounds.height);
}
}
/**
* Convenience method for getting an integer attribute from the elements
* AttributeSet.
*/
private int getIntAttr(HTML.Attribute name, int deflt) {
AttributeSet attr = getElement().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;
}
/**
* Makes sure the necessary properties and image is loaded.
*/
private void sync() {
int s = state;
if ((s & RELOAD_IMAGE_FLAG) != 0) {
sync2();
}
s = state;
if ((s & RELOAD_FLAG) != 0) {
synchronized (this) {
state = (state | RELOAD_FLAG) ^ RELOAD_FLAG;
}
setPropertiesFromAttributes();
}
}
/**
* Loads the image and updates the size accordingly. This should be invoked
* instead of invoking loadImage
or
* updateImageSize
directly.
*/
private void sync2() {
synchronized (this) {
// clear out width/height/realoadimage flag and set loading flag
state = (state | LOADING_FLAG | RELOAD_IMAGE_FLAG | WIDTH_FLAG | HEIGHT_FLAG)
^ (WIDTH_FLAG | HEIGHT_FLAG | RELOAD_IMAGE_FLAG);
image = null;
width = height = 0;
scale = 0; //vv this will cause "scale" to be reset.
}
try {
// Load the image
sync3();
// And update the size params
updateImageSize();
}
finally {
synchronized (this) {
// Clear out state in case someone threw an exception.
state = (state | LOADING_FLAG) ^ LOADING_FLAG;
}
}
}
/** vv modified to use "hook" to get image
* Loads the image from the URL getImageURL
. This should only
* be invoked from refreshImage
.
*/
private void sync3() {
width = height = 0;
scale = 0;
URL src = getImageURL();
Image newImage = null;
if (src != null) {
Dictionary cache = (Dictionary) getDocument().getProperty(IMAGE_CACHE_PROPERTY);
if (cache != null) {
newImage = (Image) cache.get(src);
}
if (newImage == null) {
//System.out.println("MyImageView, call getImage:"+src);
String hsrc = (String)getElement().getAttributes().getAttribute(HTML.Attribute.SRC);
newImage = getImage(hsrc, src);
if (cache != null && newImage != null) cache.put(src, newImage); //vv added
}
}
if (newImage != null) OAImageUtil.loadImage(newImage);
image = newImage;
}
// vv so that this can be overwritten
/**
* This is a hook to supply an image. If this returns null,
* then the myImageView will directly get the image.
*/
protected abstract Image getImage(String src, URL url);
/**
* Recreates and reloads the image. This should only be invoked from
* refreshImage
.
*/
private void updateImageSize() {
int newWidth = 0;
int newHeight = 0;
int newState = 0;
Image newImage = getImage();
if (newImage != null) {
Element elem = getElement();
// Get the width/height and set the state ivar before calling
// anything that might cause the image to be loaded, and thus the
// ImageHandler to be called.
newWidth = getIntAttr(HTML.Attribute.WIDTH, -1);
if (newWidth > 0) {
newState |= WIDTH_FLAG;
}
newHeight = getIntAttr(HTML.Attribute.HEIGHT, -1);
if (newHeight > 0) {
newState |= HEIGHT_FLAG;
}
if (newWidth <= 0) {
newWidth = newImage.getWidth(imageObserver);
if (newWidth <= 0) {
newWidth = DEFAULT_WIDTH;
}
}
if (newHeight <= 0) {
newHeight = newImage.getHeight(imageObserver);
if (newHeight <= 0) {
newHeight = DEFAULT_HEIGHT;
}
}
// Make sure the image starts loading:
if ((newState & (WIDTH_FLAG | HEIGHT_FLAG)) != 0) {
Toolkit.getDefaultToolkit().prepareImage(newImage, newWidth,
newHeight, imageObserver);
} else {
Toolkit.getDefaultToolkit().prepareImage(newImage, -1, -1,
imageObserver);
}
boolean createText = false;
synchronized (this) {
// If imageloading failed, other thread may have called
// ImageLoader which will null out image, hence we check
// for it.
if (image != null) {
if ((newState & WIDTH_FLAG) == WIDTH_FLAG || width == 0) {
width = newWidth;
}
if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG || height == 0) {
height = newHeight;
}
} else {
createText = true;
if ((newState & WIDTH_FLAG) == WIDTH_FLAG) {
width = newWidth;
}
if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG) {
height = newHeight;
}
}
state = state | newState;
state = (state | LOADING_FLAG) ^ LOADING_FLAG;
}
if (createText) {
// Only reset if this thread determined image is null
updateAltTextView();
}
} else {
width = height = DEFAULT_HEIGHT;
updateAltTextView();
}
}
/**
* Updates the view representing the alt text.
*/
private void updateAltTextView() {
String text = getAltText();
if (text != null) {
ImageLabelView newView;
newView = new ImageLabelView(getElement(), text);
synchronized (this) {
altView = newView;
}
}
}
/**
* Returns the view to use for alternate text. This may be null.
*/
private View getAltView() {
View view;
synchronized (this) {
view = altView;
}
if (view != null && view.getParent() == null) {
view.setParent(getParent());
}
return view;
}
/**
* Invokes preferenceChanged
on the event displatching thread.
*/
private void safePreferenceChanged() {
if (SwingUtilities.isEventDispatchThread()) {
Document doc = getDocument();
if (doc instanceof AbstractDocument) {
((AbstractDocument) doc).readLock();
}
preferenceChanged(null, true, true);
if (doc instanceof AbstractDocument) {
((AbstractDocument) doc).readUnlock();
}
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
safePreferenceChanged();
}
});
}
}
/**
* ImageHandler implements the ImageObserver to correctly update the display
* as new parts of the image become available.
*/
private class ImageHandler implements ImageObserver {
// This can come on any thread. If we are in the process of reloading
// the image and determining our state (loading == true) we don't fire
// preference changed, or repaint, we just reset the fWidth/fHeight as
// necessary and return. This is ok as we know when loading finishes
// it will pick up the new height/width, if necessary.
public boolean imageUpdate(Image img, int flags, int x, int y,
int newWidth, int newHeight) {
if (image == null || image != img || getParent() == null) {
return false;
}
// Bail out if there was an error:
if ((flags & (ABORT | ERROR)) != 0) {
repaint(0);
synchronized (MyImageView.this) {
if (image == img) {
// Be sure image hasn't changed since we don't
// initialy synchronize
image = null;
if ((state & WIDTH_FLAG) != WIDTH_FLAG) {
width = DEFAULT_WIDTH;
}
if ((state & HEIGHT_FLAG) != HEIGHT_FLAG) {
height = DEFAULT_HEIGHT;
}
}
if ((state & LOADING_FLAG) == LOADING_FLAG) {
// No need to resize or repaint, still in the process
// of loading.
return false;
}
}
updateAltTextView();
safePreferenceChanged();
return false;
}
// Resize image if necessary:
short changed = 0;
if ((flags & ImageObserver.HEIGHT) != 0
&& !getElement().getAttributes().isDefined(
HTML.Attribute.HEIGHT)) {
changed |= 1;
}
if ((flags & ImageObserver.WIDTH) != 0
&& !getElement().getAttributes().isDefined(
HTML.Attribute.WIDTH)) {
changed |= 2;
}
synchronized (MyImageView.this) {
if (image != img) {
return false;
}
if ((changed & 1) == 1 && (state & WIDTH_FLAG) == 0) {
width = newWidth;
}
if ((changed & 2) == 2 && (state & HEIGHT_FLAG) == 0) {
height = newHeight;
}
if ((state & LOADING_FLAG) == LOADING_FLAG) {
// No need to resize or repaint, still in the process of
// loading.
return true;
}
}
if (changed != 0) {
// May need to resize myself, asynchronously:
safePreferenceChanged();
return true;
}
// Repaint when done or when new pixels arrive:
if ((flags & (FRAMEBITS | ALLBITS)) != 0) {
repaint(0);
} else if ((flags & SOMEBITS) != 0 && sIsInc) {
repaint(sIncRate);
}
return ((flags & ALLBITS) == 0);
}
}
/**
* ImageLabelView is used if the image can't be loaded, and the attribute
* specified an alt attribute. It overriden a handle of methods as the text
* is hardcoded and does not come from the document.
*/
private class ImageLabelView extends InlineView {
private Segment segment;
private Color fg;
ImageLabelView(Element e, String text) {
super(e);
reset(text);
}
public void reset(String text) {
segment = new Segment(text.toCharArray(), 0, text.length());
}
public void paint(Graphics g, Shape a) {
// Don't use supers paint, otherwise selection will be wrong
// as our start/end offsets are fake.
GlyphPainter painter = getGlyphPainter();
if (painter != null) {
g.setColor(getForeground());
painter.paint(this, g, a, getStartOffset(), getEndOffset());
}
}
public Segment getText(int p0, int p1) {
if (p0 < 0 || p1 > segment.array.length) {
throw new RuntimeException("ImageLabelView: Stale view");
}
segment.offset = p0;
segment.count = p1 - p0;
return segment;
}
public int getStartOffset() {
return 0;
}
public int getEndOffset() {
return segment.array.length;
}
public View breakView(int axis, int p0, float pos, float len) {
// Don't allow a break
return this;
}
public Color getForeground() {
View parent;
if (fg == null && (parent = getParent()) != null) {
Document doc = getDocument();
AttributeSet attr = parent.getAttributes();
if (attr != null && (doc instanceof StyledDocument)) {
fg = ((StyledDocument) doc).getForeground(attr);
}
}
return fg;
}
}
}