001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Alexey A. Ivanov
019: * @version $Revision$
020: */package javax.swing.text.html;
021:
022: import java.awt.Color;
023: import java.awt.Container;
024: import java.awt.Font;
025: import java.awt.FontMetrics;
026: import java.awt.Graphics;
027: import java.awt.Image;
028: import java.awt.Rectangle;
029: import java.awt.Shape;
030: import java.awt.Toolkit;
031: import java.net.URL;
032:
033: import javax.swing.Icon;
034: import javax.swing.event.DocumentEvent;
035: import javax.swing.text.AttributeSet;
036: import javax.swing.text.BadLocationException;
037: import javax.swing.text.Element;
038: import javax.swing.text.Highlighter;
039: import javax.swing.text.JTextComponent;
040: import javax.swing.text.LayeredHighlighter;
041: import javax.swing.text.View;
042: import javax.swing.text.ViewFactory;
043: import javax.swing.text.Position.Bias;
044: import javax.swing.text.html.CSS.ColorProperty;
045:
046: import org.apache.harmony.x.swing.text.html.HTMLIconFactory;
047:
048: public class ImageView extends View {
049:
050: private AttributeSet attrs;
051:
052: private BackgroundImageLoader loader;
053: private String src;
054:
055: private boolean synchronous = false;
056:
057: private Color color;
058:
059: private int border;
060: private int vSpace;
061: private int hSpace;
062:
063: /** Not-found-property marker: Any negative number */
064: private final int INT_PROPERTY_NOT_FOUND = -1;
065:
066: public ImageView(final Element element) {
067: super (element);
068: if (element != null) { // Fix for HARMONY-1747, for compatibility with RI
069: setPropertiesFromAttributes();
070: if (element.getAttributes().getAttribute(HTML.Tag.A) != null) {
071: setAnchorViewAttributes();
072: }
073: adjustBordersAndSpaces();
074: }
075: }
076:
077: public Image getImage() {
078: return loader.getImage();
079: }
080:
081: public URL getImageURL() {
082: URL base = ((HTMLDocument) getDocument()).getBase();
083: return HTML.resolveURL(src, base);
084: }
085:
086: public Icon getLoadingImageIcon() {
087: return HTMLIconFactory.getLoadingImageIcon();
088: }
089:
090: public Icon getNoImageIcon() {
091: return HTMLIconFactory.getNoImageIcon();
092: }
093:
094: public void setLoadsSynchronously(final boolean synchronous) {
095: this .synchronous = synchronous;
096: }
097:
098: public boolean getLoadsSynchronously() {
099: return synchronous;
100: }
101:
102: @Override
103: public float getPreferredSpan(final int axis) {
104: if (loader.isError()) {
105: String alt = getAltText();
106: FontMetrics metrics = null;
107: if (alt != null) {
108: Font font = getStyleSheet().getFont(getAttributes());
109: metrics = Toolkit.getDefaultToolkit().getFontMetrics(
110: font);
111: }
112:
113: return axis == X_AXIS ? getNoImageIcon().getIconWidth()
114: + 2
115: * border
116: + 2
117: * hSpace
118: + ((metrics == null) ? 0 : metrics.stringWidth(alt))
119: : ((metrics == null) ? getNoImageIcon()
120: .getIconHeight() : Math.max(
121: getNoImageIcon().getIconHeight(), metrics
122: .getHeight())
123: + 2 * border + 2 * vSpace);
124: }
125: if (!loader.isReady()) {
126: return axis == X_AXIS ? getLoadingImageIcon()
127: .getIconWidth()
128: + 2 * border + 2 * hSpace : getLoadingImageIcon()
129: .getIconHeight()
130: + 2 * border + 2 * vSpace;
131: }
132: if (axis == X_AXIS) {
133: return loader.getWidth() + 2 * border + 2 * hSpace;
134: }
135: return loader.getHeight() + 2 * border + 2 * vSpace;
136: }
137:
138: @Override
139: public String getToolTipText(final float x, final float y,
140: final Shape shape) {
141: return getAltText();
142: }
143:
144: public String getAltText() {
145: return (String) getElement().getAttributes().getAttribute(
146: HTML.Attribute.ALT);
147: }
148:
149: @Override
150: public void paint(final Graphics g, final Shape shape) {
151:
152: Rectangle rc = shape.getBounds();
153: rc.setSize(rc.width - 2 * (hSpace + border), rc.height - 2
154: * (vSpace + border));
155:
156: // TODO change layered highlight painting code
157: JTextComponent tc = (JTextComponent) getContainer();
158: Highlighter hl = tc.getHighlighter();
159: if (hl instanceof LayeredHighlighter) {
160: ((LayeredHighlighter) hl).paintLayeredHighlights(g,
161: getStartOffset(), getEndOffset(), shape, tc, this );
162: }
163:
164: Color oldColor = g.getColor();
165: if (border > 0) {
166: g.setColor(color);
167: g.fillRect(rc.x + hSpace, rc.y + vSpace, rc.width + 2
168: * border, rc.height + 2 * border);
169: g.setColor(oldColor);
170: g.fillRect(rc.x + hSpace + border, rc.y + vSpace + border,
171: rc.width, rc.height);
172: }
173:
174: if (loader.isError()) {
175:
176: g.setColor(color);
177:
178: getNoImageIcon().paintIcon(null, g, rc.x + hSpace + border,
179: rc.y + vSpace + border);
180:
181: String alt = getAltText();
182: if (alt != null) {
183: Font oldFont = g.getFont();
184:
185: Font font = getStyleSheet().getFont(getAttributes());
186: g.setFont(font);
187: FontMetrics metrics = g.getFontMetrics();
188: g.drawString(alt, rc.x + hSpace + border
189: + getNoImageIcon().getIconWidth(), rc.y
190: + vSpace + border + metrics.getAscent());
191:
192: g.setFont(oldFont);
193: }
194:
195: g.setColor(oldColor);
196: return;
197: }
198:
199: if (!loader.isReady()) {
200: if (!synchronous) {
201: getLoadingImageIcon().paintIcon(null, g, rc.x, rc.y);
202: return;
203: }
204: }
205:
206: g.drawImage(getImage(), rc.x + hSpace + border, rc.y + vSpace
207: + border, rc.width, rc.height, loader);
208: }
209:
210: @Override
211: public Shape modelToView(final int pos, final Shape shape,
212: final Bias bias) throws BadLocationException {
213:
214: Rectangle rc = shape.getBounds();
215: if (pos <= getStartOffset()) {
216: return new Rectangle(rc.x, rc.y, 0, rc.height);
217: }
218: return new Rectangle(rc.x + rc.width, rc.y, 0, rc.height);
219: }
220:
221: @Override
222: public int viewToModel(final float x, final float y,
223: final Shape shape, final Bias[] bias) {
224:
225: Rectangle rc = shape.getBounds();
226: if (x < rc.x + rc.width/* / 2*/) {
227: bias[0] = Bias.Forward;
228: return getStartOffset();
229: }
230: bias[0] = Bias.Backward;
231: return getEndOffset();
232: }
233:
234: @Override
235: public float getAlignment(final int axis) {
236: if (axis == Y_AXIS) {
237: return 1;
238: }
239: return super .getAlignment(axis);
240: }
241:
242: @Override
243: public AttributeSet getAttributes() {
244: return attrs;
245: }
246:
247: @Override
248: public void changedUpdate(final DocumentEvent event,
249: final Shape shape, final ViewFactory factory) {
250: setPropertiesFromAttributes();
251: super .changedUpdate(event, shape, factory);
252: }
253:
254: protected StyleSheet getStyleSheet() {
255: return ((HTMLDocument) getDocument()).getStyleSheet();
256: }
257:
258: protected void setPropertiesFromAttributes() {
259: attrs = getStyleSheet().getViewAttributes(this );
260:
261: AttributeSet elAttrs = getElement().getAttributes();
262:
263: src = (String) elAttrs.getAttribute(HTML.Attribute.SRC);
264:
265: border = getIntProperty(elAttrs, HTML.Attribute.BORDER);
266:
267: hSpace = getIntProperty(elAttrs, HTML.Attribute.HSPACE);
268:
269: vSpace = getIntProperty(elAttrs, HTML.Attribute.VSPACE);
270:
271: Object size = getAttributes().getAttribute(CSS.Attribute.WIDTH);
272: int desiredWidth = -1;
273: if (size instanceof CSS.Length) {
274: desiredWidth = ((CSS.Length) size).intValue(this );
275: }
276:
277: size = getAttributes().getAttribute(CSS.Attribute.HEIGHT);
278: int desiredHeight = -1;
279: if (size instanceof CSS.Length) {
280: desiredHeight = ((CSS.Length) size).intValue(this );
281: }
282: createImage(desiredWidth, desiredHeight);
283:
284: color = getStyleSheet().getForeground(getAttributes());
285: }
286:
287: /**
288: * Converts attribute value to number, correctly interprets by this view
289: * (i.e. null->negative number)
290: */
291: private int getIntProperty(AttributeSet source, HTML.Attribute attr) {
292: String result = (String) source.getAttribute(attr);
293: // Null verification is added for possibly improved performance:
294: // throwing and
295: // catching an exception is slower than null verification
296: if (result != null) {
297: try {
298: return Integer.parseInt(result);
299: } catch (NumberFormatException nfe) {
300: // Ignored, according to RI's result
301: }
302: }
303: return INT_PROPERTY_NOT_FOUND;
304: }
305:
306: private void createImage(final int desiredWidth,
307: final int desiredHeight) {
308: loader = new BackgroundImageLoader(getImageURL(), synchronous,
309: desiredWidth, desiredHeight) {
310: @Override
311: protected void onReady() {
312: super .onReady();
313: update();
314: }
315:
316: @Override
317: protected void onError() {
318: super .onError();
319: update();
320: }
321:
322: private void update() {
323: preferenceChanged(ImageView.this , true, true);
324: final Container component = getContainer();
325: if (component != null) {
326: component.repaint();
327: }
328: }
329: };
330: }
331:
332: /**
333: * The method sets the 1px border (if the border is absent) and sets the
334: * color stated for <a> tag
335: */
336: private void setAnchorViewAttributes() {
337: if (border < 0) {
338: border = 1;
339: }
340: color = ((ColorProperty) getStyleSheet().getRule("a")
341: .getAttribute(CSS.Attribute.COLOR)).getColor();
342: }
343:
344: /**
345: * Sets negative properties to zero ones (negative property can either
346: * directly stated or returned by getIntProperty method)
347: */
348: private void adjustBordersAndSpaces() {
349: if (vSpace < 0) {
350: vSpace = 0;
351: }
352: if (hSpace < 0) {
353: hSpace = 0;
354: }
355: if (border < 0) {
356: border = 0;
357: }
358: }
359: }
|