html images and chat document size problems fixed

cusax-fix
Yana Stamcheva 19 years ago
parent 3dc9d918d1
commit db1a225990

@ -426,7 +426,8 @@ public void insertMessageAfterStart(String chatString)
private void ensureDocumentSize()
{
if (document.getLength() > Constants.CHAT_BUFFER_SIZE)
if (document.getLength() > Constants.CHAT_BUFFER_SIZE
&& document.getDefaultRootElement().getElementCount() > 2)
{
Element firstElement
= this.document.getDefaultRootElement().getElement(0);
@ -633,6 +634,11 @@ private String processSmilies(String message, String contentType)
}
else
{
if (HTML_CONTENT_TYPE.equals(contentType))
{
message = removeAltFromHTMLSmilies(message);
}
startPlainTextTag = "";
endPlainTextTag = "";
}
@ -683,6 +689,64 @@ private String processSmilies(String message, String contentType)
return msgBuffer.toString();
}
/**
* Removes alt content from the smilies contained in html messages. This
* is needed in order to process html smilies properly. If ':)' is not
* removed, then it will be replaced by the html <img> tag in the
* processSmilies method and this will cause having an <img> tag within
* another <img> tag and a malformatted html.
*
* @param content the message content
* @return formatted messages content
*/
private String removeAltFromHTMLSmilies(String content)
{
StringBuffer result = new StringBuffer();
StringBuffer smileyRegexp = new StringBuffer();
//building supported smilies list
ArrayList smiliesList = ImageLoader.getDefaultSmiliesPack();
for (int i = 0; i < smiliesList.size(); i++)
{
Smiley smiley = (Smiley) smiliesList.get(i);
String[] smileyStrings = smiley.getSmileyStrings();
for(int j = 0; j < smileyStrings.length; j++)
{
smileyRegexp.append(GuiUtils.replaceSpecialRegExpChars(
smileyStrings[j])).append("|");
}
}
smileyRegexp.deleteCharAt(smileyRegexp.length() - 1);
//creating pattern string. we want to remove all smilies that appear
//in the 'alt' attribute of an '<img>' tag, like <img alt=":)" src=... >
String imgPattern = "<img (.*alt=\"|')(.*?)"
+ smileyRegexp.toString()
+ "(.*?)(\"|')(.*?)>";
Pattern p = Pattern.compile(imgPattern, Pattern.CASE_INSENSITIVE);
Matcher imgFinder = p.matcher(content);
boolean foundMatch = false;
//creating escaped message
while (imgFinder.find())
{
if (!foundMatch)
foundMatch = true;
String matchGroup = imgFinder.group();
String replacement =
matchGroup.replaceAll(smileyRegexp.toString(), "");
imgFinder.appendReplacement(result,
GuiUtils.replaceSpecialRegExpChars(replacement));
}
imgFinder.appendTail(result);
return result.toString();
}
/**
* Opens a link in the default browser when clicked and shows link url in a
* popup on mouseover.

@ -19,6 +19,8 @@
import javax.swing.text.*;
import javax.swing.text.html.*;
import net.java.sip.communicator.util.*;
/*
* The content of this file was based on code borrowed from Rob Kenworthy,
* http://www.javaworld.com/javaworld/javatips/jw-javatip109.html#resources.
@ -26,25 +28,34 @@
/**
* The <tt>SIPCommImageView</tt> is an <tt>ImageView</tt> which allows to
* specify a related path when describing an image within an html document.
*
*
* @author Yana Stamcheva
*/
public class SIPCommImageView extends ImageView
implements ImageObserver {
public class SIPCommImageView
extends ImageView
implements ImageObserver
{
private Logger logger = Logger.getLogger(SIPCommImageView.class);
public static final String TOP = "top";
public static final String TEXTTOP = "texttop";
public static final String MIDDLE = "middle";
public static final String ABSMIDDLE = "absmiddle";
public static final String CENTER = "center";
public static final String BOTTOM = "bottom";
/**
* Creates a new view that represents an IMG element.
*
*
* @param elem the element to create a view for.
*/
public SIPCommImageView(Element elem) {
public SIPCommImageView(Element elem)
{
super(elem);
@ -57,11 +68,13 @@ public SIPCommImageView(Element elem) {
/**
* Initializes this view.
*
*
* @param elem the element.
*/
private void initialize(Element elem) {
synchronized (this) {
private void initialize(Element elem)
{
synchronized (this)
{
loading = true;
fWidth = fHeight = 0;
}
@ -71,47 +84,63 @@ private void initialize(Element elem) {
boolean customWidth = false;
boolean customHeight = false;
try {
try
{
fElement = elem;
// Request image from document's cache:
AttributeSet attr = elem.getAttributes();
if (isURL()) {
if (isURL())
{
URL src = getSourceURL();
if (src != null) {
if (src != null)
{
Dictionary cache = (Dictionary) getDocument().getProperty(
IMAGE_CACHE_PROPERTY);
IMAGE_CACHE_PROPERTY);
if (cache != null)
fImage = (Image) cache.get(src);
else
fImage = Toolkit.getDefaultToolkit().getImage(src);
}
} else {
}
else
{
/*--Code to load from relative path--*/
String src = (String) fElement.getAttributes().getAttribute(
HTML.Attribute.SRC);
HTML.Attribute.SRC);
// fImage = Toolkit.getDefaultToolkit().createImage(src);
try {
try
{
fImage = ImageIO.read(ImageLoader.class.getClassLoader()
.getResource(src));
.getResource(src));
try {
try
{
waitForImage();
} catch (InterruptedException e) {
}
catch (InterruptedException e)
{
fImage = null;
}
} catch (IOException e) {
e.printStackTrace();
}
/*----------------------------------*/
catch (IOException e)
{
fImage = null;
logger.error("Failed to read image.", e);
}
catch (IllegalArgumentException iae)
{
fImage = null;
logger.error("Failed to read image.", iae);
}
}
// Get height/width from params or image or defaults:
@ -139,51 +168,59 @@ private void initialize(Element elem) {
if (fImage != null)
if (customWidth && customHeight)
Toolkit.getDefaultToolkit().prepareImage(fImage, height,
width, this);
width, this);
else
Toolkit.getDefaultToolkit().prepareImage(fImage, -1, -1,
this);
} finally {
this);
} finally
{
synchronized (this) {
synchronized (this)
{
loading = false;
if (customWidth || fWidth == 0) {
if (customWidth || fWidth == 0)
{
fWidth = width;
}
if (customHeight || fHeight == 0) {
if (customHeight || fHeight == 0)
{
fHeight = height;
}
}
}
}
/**
* Determines if path is in the form of an URL.
*
* @return TRUE if the source is URL, FALSE otherwise.
*/
private boolean isURL() {
private boolean isURL()
{
String src = (String) fElement.getAttributes().getAttribute(
HTML.Attribute.SRC);
HTML.Attribute.SRC);
return src.toLowerCase().startsWith("file")
|| src.toLowerCase().startsWith("http");
|| src.toLowerCase().startsWith("http");
}
/**
* Make sure an image is loaded - ie no broken images. So far its used only
* for images loaded off the disk (non-URL).
*
* @throws InterruptedException
*/
private void waitForImage() throws InterruptedException {
private void waitForImage() throws InterruptedException
{
int w = fImage.getWidth(this);
int h = fImage.getHeight(this);
while (true) {
while (true)
{
int flags = Toolkit.getDefaultToolkit().checkImage(fImage, w, h,
this);
this);
if (((flags & ERROR) != 0) || ((flags & ABORT) != 0))
throw new InterruptedException();
@ -199,7 +236,8 @@ else if ((flags & (ALLBITS | FRAMEBITS)) != 0)
* Fetches the attributes to use when rendering. This is implemented to
* multiplex the attributes specified in the model with a StyleSheet.
*/
public AttributeSet getAttributes() {
public AttributeSet getAttributes()
{
return attr;
}
@ -207,7 +245,8 @@ public AttributeSet getAttributes() {
/**
* Checks if the image is within a link.
*/
boolean isLink() {
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:
@ -215,7 +254,8 @@ boolean isLink() {
fElement.getAttributes().getAttribute(HTML.Tag.A);
if (anchorAttr != null) {
if (anchorAttr != null)
{
return anchorAttr.isDefined(HTML.Attribute.HREF);
}
return false;
@ -224,22 +264,25 @@ boolean isLink() {
/**
* Returns the size of the border to use.
*/
int getBorder() {
int getBorder()
{
return getIntAttr(HTML.Attribute.BORDER, isLink() ? DEFAULT_BORDER : 0);
}
/**
* Returns the amount of extra space to add along an axis.
*/
int getSpace(int axis) {
int getSpace(int axis)
{
return getIntAttr(axis == X_AXIS ? HTML.Attribute.HSPACE
: HTML.Attribute.VSPACE, 0);
: HTML.Attribute.VSPACE, 0);
}
/**
* Returns the border's color, or null if this is not a link.
*/
Color getBorderColor() {
Color getBorderColor()
{
StyledDocument doc = (StyledDocument) getDocument();
return doc.getForeground(getAttributes());
}
@ -247,15 +290,17 @@ Color getBorderColor() {
/**
* Returns the image's vertical alignment.
*/
float getVerticalAlignment() {
float getVerticalAlignment()
{
String align = (String) fElement.getAttributes().getAttribute(
HTML.Attribute.ALIGN);
if (align != null) {
HTML.Attribute.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))
|| align.equals(ABSMIDDLE))
return 0.5f;
}
@ -264,32 +309,38 @@ else if (align.equals(this.CENTER) || align.equals(MIDDLE)
/**
* Checks if the image is at least one pixel.
*
* @param obs The <tt>ImageObserver</tt>.
* @return <code>true</code> if the image is not null and is at least one
* pixel big, <code>false</code> otherwise.
* pixel big, <code>false</code> otherwise.
*/
boolean hasPixels(ImageObserver obs) {
boolean hasPixels(ImageObserver obs)
{
return fImage != null && fImage.getHeight(obs) > 0
&& fImage.getWidth(obs) > 0;
&& fImage.getWidth(obs) > 0;
}
/**
* Returns an URL for the image source, or null if it could not be
* determined.
*/
private URL getSourceURL() {
private URL getSourceURL()
{
String src = (String) fElement.getAttributes().getAttribute(
HTML.Attribute.SRC);
HTML.Attribute.SRC);
if (src == null)
return null;
URL reference = ((HTMLDocument) getDocument()).getBase();
try {
try
{
URL u = new URL(reference, src);
return u;
} catch (MalformedURLException e) {
}
catch (MalformedURLException e)
{
return null;
}
}
@ -297,21 +348,27 @@ private URL getSourceURL() {
/**
* Look up an integer-valued attribute. <b>Not</b> recursive.
*/
private int getIntAttr(HTML.Attribute name, int deflt) {
private int getIntAttr(HTML.Attribute name, int deflt)
{
AttributeSet attr = fElement.getAttributes();
if (attr.isDefined(name)) { // does not check parents!
if (attr.isDefined(name))
{ // does not check parents!
int i;
String val = (String) attr.getAttribute(name);
if (val == null)
i = deflt;
else
try {
try
{
i = Math.max(0, Integer.parseInt(val));
} catch (NumberFormatException x) {
}
catch (NumberFormatException x)
{
i = deflt;
}
return i;
} else
}
else
return deflt;
}
@ -319,10 +376,12 @@ private int getIntAttr(HTML.Attribute name, int deflt) {
* Establishes the parent view for this view. Seize this moment to cache the
* AWT Container I'm in.
*/
public void setParent(View parent) {
public void setParent(View parent)
{
super.setParent(parent);
fContainer = parent != null ? getContainer() : null;
if (parent == null && fComponent != null) {
if (parent == null && fComponent != null)
{
fComponent.getParent().remove(fComponent);
fComponent = null;
}
@ -351,12 +410,13 @@ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f)
/**
* 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) {
public void paint(Graphics g, Shape a)
{
Color oldColor = g.getColor();
fBounds = a.getBounds();
int border = getBorder();
@ -367,7 +427,8 @@ public void paint(Graphics g, Shape a) {
int sel = getSelectionState();
// If no pixels yet, draw gray outline and icon:
if (!hasPixels(this)) {
if (!hasPixels(this))
{
g.setColor(Color.lightGray);
g.drawRect(x, y, width - 1, height - 1);
g.setColor(oldColor);
@ -378,7 +439,8 @@ public void paint(Graphics g, Shape a) {
}
// Draw image:
if (fImage != null) {
if (fImage != null)
{
g.drawImage(fImage, x, y, width, height, this);
// Use the following instead of g.drawImage when
// BufferedImageGraphics2D.setXORMode is fixed (4158822).
@ -393,10 +455,12 @@ public void paint(Graphics g, Shape a) {
// If selected exactly, we need a black border & grow-box:
Color bc = getBorderColor();
if (sel == 2) {
if (sel == 2)
{
// Make sure there's room for a border:
int delta = 2 - border;
if (delta > 0) {
if (delta > 0)
{
x += delta;
y += delta;
width -= delta << 1;
@ -410,7 +474,8 @@ public void paint(Graphics g, Shape a) {
}
// Draw border:
if (border > 0) {
if (border > 0)
{
if (bc != null)
g.setColor(bc);
// Draw a thick rectangle:
@ -424,28 +489,33 @@ public void paint(Graphics g, Shape a) {
* 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) {
protected void repaint(long delay)
{
if (fContainer != null && fBounds != null)
{
fContainer.repaint(delay, fBounds.x, fBounds.y, fBounds.width,
fBounds.height);
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.
* "Exclusive" selection is only returned when editable.
*/
protected int getSelectionState() {
protected int getSelectionState()
{
int p0 = fElement.getStartOffset();
int p1 = fElement.getEndOffset();
if (fContainer instanceof JTextComponent) {
if (fContainer instanceof JTextComponent)
{
JTextComponent textComp = (JTextComponent) fContainer;
int start = textComp.getSelectionStart();
int end = textComp.getSelectionEnd();
if (start <= p0 && end >= p1) {
if (start <= p0 && end >= p1)
{
if (start == p0 && end == p1 && isEditable())
return 2;
else
@ -457,18 +527,21 @@ protected int getSelectionState() {
/**
* Checks whether the container is editable.
*
* @return <code>true</code> if the container is editable,
* <code>false</code> otherwise.
* <code>false</code> otherwise.
*/
protected boolean isEditable() {
protected boolean isEditable()
{
return fContainer instanceof JEditorPane
&& ((JEditorPane) fContainer).isEditable();
&& ((JEditorPane) fContainer).isEditable();
}
/**
* Returns the text editor's highlight color.
*/
protected Color getHighlightColor() {
protected Color getHighlightColor()
{
JTextComponent textComp = (JTextComponent) fContainer;
return textComp.getSelectionColor();
}
@ -481,12 +554,14 @@ protected Color getHighlightColor() {
// 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 width,
int height) {
int height)
{
if (fImage == null || fImage != img)
return false;
// Bail out if there was an error:
if ((flags & (ABORT | ERROR)) != 0) {
if ((flags & (ABORT | ERROR)) != 0)
{
fImage = null;
repaint(0);
return false;
@ -495,36 +570,47 @@ public boolean imageUpdate(Image img, int flags, int x, int y, int width,
// Resize image if necessary:
short changed = 0;
if ((flags & ImageObserver.HEIGHT) != 0)
if (!getElement().getAttributes().isDefined(HTML.Attribute.HEIGHT)) {
if (!getElement().getAttributes().isDefined(HTML.Attribute.HEIGHT))
{
changed |= 1;
}
if ((flags & ImageObserver.WIDTH) != 0)
if (!getElement().getAttributes().isDefined(HTML.Attribute.WIDTH)) {
if (!getElement().getAttributes().isDefined(HTML.Attribute.WIDTH))
{
changed |= 2;
}
synchronized (this) {
if ((changed & 1) == 1) {
synchronized (this)
{
if ((changed & 1) == 1)
{
fWidth = width;
}
if ((changed & 2) == 2) {
if ((changed & 2) == 2)
{
fHeight = height;
}
if (loading) {
if (loading)
{
// No need to resize or repaint, still in the process of
// loading.
return true;
}
}
if (changed != 0) {
if (changed != 0)
{
// May need to resize myself, asynchronously:
Document doc = getDocument();
try {
if (doc instanceof AbstractDocument) {
try
{
if (doc instanceof AbstractDocument)
{
((AbstractDocument) doc).readLock();
}
preferenceChanged(this, true, true);
} finally {
if (doc instanceof AbstractDocument) {
} finally
{
if (doc instanceof AbstractDocument)
{
((AbstractDocument) doc).readUnlock();
}
}
@ -544,7 +630,7 @@ else if ((flags & SOMEBITS) != 0)
/*
* Static properties for incremental drawing. Swiped from Component.java
*
*
* @see #imageUpdate
*/
private static boolean sIsInc = true;
@ -555,17 +641,18 @@ else if ((flags & SOMEBITS) != 0)
/**
* Determines the preferred span for this view along an axis.
*
* @param axis
* may be either X_AXIS or Y_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) {
public float getPreferredSpan(int axis)
{
int extra = 2 * (getBorder() + getSpace(axis));
switch (axis) {
switch (axis)
{
case View.X_AXIS:
return fWidth + extra;
case View.Y_AXIS:
@ -579,16 +666,17 @@ public float getPreferredSpan(int 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
*
* @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) {
public float getAlignment(int axis)
{
switch (axis)
{
case View.Y_AXIS:
return getVerticalAlignment();
default:
@ -599,24 +687,24 @@ public float getAlignment(int 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
*
* @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
* @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 {
throws BadLocationException
{
int p0 = getStartOffset();
int p1 = getEndOffset();
if ((pos >= p0) && (pos <= p1)) {
if ((pos >= p0) && (pos <= p1))
{
Rectangle r = a.getBounds();
if (pos == p1) {
if (pos == p1)
{
r.x += r.width;
}
r.width = 0;
@ -628,20 +716,19 @@ public Shape modelToView(int pos, Shape a, Position.Bias b)
/**
* 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
*
* @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) {
public int viewToModel(float x, float y, Shape a, Position.Bias[] bias)
{
Rectangle alloc = (Rectangle) a;
if (x < alloc.x + alloc.width) {
if (x < alloc.x + alloc.width)
{
bias[0] = Position.Bias.Forward;
return getStartOffset();
}
@ -651,20 +738,20 @@ public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
/**
* Set the size of the view. (Ignored.)
*
* @param width
* the width
* @param height
* the height
*
* @param width the width
* @param height the height
*/
public void setSize(float width, float 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!
}
// --- Static icon accessors -------------------------------------------
private Icon makeIcon(final String gifFile) throws IOException {
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
@ -672,43 +759,51 @@ private Icon makeIcon(final String gifFile) throws IOException {
* returns raw bytes, which we can convert to an image.
*/
InputStream resource = SIPCommImageView.class
.getResourceAsStream(gifFile);
.getResourceAsStream(gifFile);
if (resource == null) {
if (resource == null)
{
System.err.println(SIPCommImageView.class.getName() + "/" + gifFile
+ " not found.");
+ " 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) {
while ((n = in.read(buffer)) > 0)
{
out.write(buffer, 0, n);
}
in.close();
out.flush();
buffer = out.toByteArray();
if (buffer.length == 0) {
if (buffer.length == 0)
{
System.err.println("warning: " + gifFile + " is zero-length");
return null;
}
return new ImageIcon(buffer);
}
private void loadIcons() {
try {
private void loadIcons()
{
try
{
if (sPendingImageIcon == null)
sPendingImageIcon = makeIcon(PENDING_IMAGE_SRC);
if (sMissingImageIcon == null)
sMissingImageIcon = makeIcon(MISSING_IMAGE_SRC);
} catch (Exception x) {
}
catch (Exception x)
{
System.err.println("ImageView: Couldn't load image icons");
}
}
protected StyleSheet getStyleSheet() {
protected StyleSheet getStyleSheet()
{
HTMLDocument doc = (HTMLDocument) getDocument();
return doc.getStyleSheet();
}
@ -733,24 +828,28 @@ protected StyleSheet getStyleSheet() {
private boolean fGrowProportionally; // should grow be proportional?
/** Set to true, while the receiver is locked, to indicate the reciever
* is loading the image. This is used in imageUpdate. */
/**
* Set to true, while the receiver is locked, to indicate the reciever is
* loading the image. This is used in imageUpdate.
*/
private boolean loading;
// --- 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 String PENDING_IMAGE_SRC = "icons/image-delayed.gif", // both
// stolen
// from
// HotJava
MISSING_IMAGE_SRC = "icons/image-failed.gif";
//$ move this someplace public
// $ 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;
// Default value of BORDER param: //? possibly move into stylesheet?
DEFAULT_BORDER = 2;
}

Loading…
Cancel
Save