71bdf3888aecff3158512e5c9212f516049ed8ab
[WebKit.git] / WebCore / html / html_imageimpl.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
6  * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include "config.h"
25 #include "html_imageimpl.h"
26
27 #include "DocLoader.h"
28 #include "EventNames.h"
29 #include "HTMLFormElement.h"
30 #include "IntPointArray.h"
31 #include "csshelper.h"
32 #include "CSSPropertyNames.h"
33 #include "CSSValueKeywords.h"
34 #include "HTMLDocument.h"
35 #include "RenderImage.h"
36 #include "HTMLNames.h"
37
38 using namespace std;
39
40 namespace WebCore {
41
42 using namespace EventNames;
43 using namespace HTMLNames;
44
45 HTMLImageLoader::HTMLImageLoader(Element* elt)
46     : m_element(elt), m_image(0), m_firedLoad(true), m_imageComplete(true)
47 {
48 }
49
50 HTMLImageLoader::~HTMLImageLoader()
51 {
52     if (m_image)
53         m_image->deref(this);
54     m_element->document()->removeImage(this);
55 }
56
57 void HTMLImageLoader::setLoadingImage(CachedImage *loadingImage)
58 {
59     m_firedLoad = false;
60     m_imageComplete = false;
61     m_image = loadingImage;
62 }
63
64 void HTMLImageLoader::updateFromElement()
65 {
66     // If we're not making renderers for the page, then don't load images.  We don't want to slow
67     // down the raw HTML parsing case by loading images we don't intend to display.
68     Element* elem = element();
69     Document* doc = elem->document();
70     if (!doc->renderer())
71         return;
72
73     AtomicString attr = elem->getAttribute(elem->hasLocalName(objectTag) ? dataAttr : srcAttr);
74     
75     // Treat a lack of src or empty string for src as no image at all.
76     CachedImage *newImage = 0;
77     if (!attr.isEmpty())
78         newImage = doc->docLoader()->requestImage(parseURL(attr));
79
80     CachedImage *oldImage = m_image;
81     if (newImage != oldImage) {
82 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
83         if (!doc->ownerElement() && newImage)
84             printf("Image requested at %d\n", doc->elapsedTime());
85 #endif
86         setLoadingImage(newImage);
87         if (newImage)
88             newImage->ref(this);
89         if (oldImage)
90             oldImage->deref(this);
91     }
92
93     if (RenderImage* renderer = static_cast<RenderImage*>(elem->renderer()))
94         renderer->resetAnimation();
95 }
96
97 void HTMLImageLoader::dispatchLoadEvent()
98 {
99     if (!m_firedLoad && m_image) {
100         m_firedLoad = true;
101         element()->dispatchHTMLEvent(m_image->isErrorImage() ? errorEvent : loadEvent, false, false);
102     }
103 }
104
105 void HTMLImageLoader::notifyFinished(CachedObject *image)
106 {
107     m_imageComplete = true;
108     Element* elem = element();
109     Document* doc = elem->document();
110     doc->dispatchImageLoadEventSoon(this);
111 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
112         if (!doc->ownerElement())
113             printf("Image loaded at %d\n", doc->elapsedTime());
114 #endif
115     if (RenderImage* renderer = static_cast<RenderImage*>(elem->renderer()))
116         renderer->setCachedImage(m_image);
117 }
118
119 // -------------------------------------------------------------------------
120
121 HTMLImageElement::HTMLImageElement(Document *doc, HTMLFormElement *f)
122     : HTMLElement(imgTag, doc), m_imageLoader(this), ismap(false), m_form(f)
123 {
124     if (f)
125         f->registerImgElement(this);
126 }
127
128 HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document *doc)
129     : HTMLElement(tagName, doc), m_imageLoader(this), ismap(false), m_form(0)
130 {
131 }
132
133 HTMLImageElement::~HTMLImageElement()
134 {
135     if (m_form)
136         m_form->removeImgElement(this);
137 }
138
139 bool HTMLImageElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
140 {
141     if (attrName == widthAttr ||
142         attrName == heightAttr ||
143         attrName == vspaceAttr ||
144         attrName == hspaceAttr ||
145         attrName == valignAttr) {
146         result = eUniversal;
147         return false;
148     }
149     
150     if (attrName == borderAttr || attrName == alignAttr) {
151         result = eReplaced; // Shared with embed and iframe elements.
152         return false;
153     }
154
155     return HTMLElement::mapToEntry(attrName, result);
156 }
157
158 void HTMLImageElement::parseMappedAttribute(MappedAttribute *attr)
159 {
160     const QualifiedName& attrName = attr->name();
161     if (attrName == altAttr) {
162         if (renderer())
163             static_cast<RenderImage*>(renderer())->updateAltText();
164     } else if (attrName == srcAttr)
165         m_imageLoader.updateFromElement();
166     else if (attrName == widthAttr)
167         addCSSLength(attr, CSS_PROP_WIDTH, attr->value());
168     else if (attrName == heightAttr)
169         addCSSLength(attr, CSS_PROP_HEIGHT, attr->value());
170     else if (attrName == borderAttr) {
171         // border="noborder" -> border="0"
172         if(attr->value().toInt()) {
173             addCSSLength(attr, CSS_PROP_BORDER_WIDTH, attr->value());
174             addCSSProperty(attr, CSS_PROP_BORDER_TOP_STYLE, CSS_VAL_SOLID);
175             addCSSProperty(attr, CSS_PROP_BORDER_RIGHT_STYLE, CSS_VAL_SOLID);
176             addCSSProperty(attr, CSS_PROP_BORDER_BOTTOM_STYLE, CSS_VAL_SOLID);
177             addCSSProperty(attr, CSS_PROP_BORDER_LEFT_STYLE, CSS_VAL_SOLID);
178         }
179     } else if (attrName == vspaceAttr) {
180         addCSSLength(attr, CSS_PROP_MARGIN_TOP, attr->value());
181         addCSSLength(attr, CSS_PROP_MARGIN_BOTTOM, attr->value());
182     } else if (attrName == hspaceAttr) {
183         addCSSLength(attr, CSS_PROP_MARGIN_LEFT, attr->value());
184         addCSSLength(attr, CSS_PROP_MARGIN_RIGHT, attr->value());
185     } else if (attrName == alignAttr)
186         addHTMLAlignment(attr);
187     else if (attrName == valignAttr)
188         addCSSProperty(attr, CSS_PROP_VERTICAL_ALIGN, attr->value());
189     else if (attrName == usemapAttr) {
190         if (attr->value().domString()[0] == '#')
191             usemap = attr->value();
192         else
193             usemap = document()->completeURL(parseURL(attr->value()));
194         m_isLink = !attr->isNull();
195     } else if (attrName == ismapAttr)
196         ismap = true;
197     else if (attrName == onabortAttr)
198         setHTMLEventListener(abortEvent, attr);
199     else if (attrName == onerrorAttr)
200         setHTMLEventListener(errorEvent, attr);
201     else if (attrName == onloadAttr)
202         setHTMLEventListener(loadEvent, attr);
203     else if (attrName == compositeAttr)
204         _compositeOperator = attr->value().domString();
205     else if (attrName == nameAttr) {
206         String newNameAttr = attr->value();
207         if (inDocument() && document()->isHTMLDocument()) {
208             HTMLDocument* doc = static_cast<HTMLDocument*>(document());
209             doc->removeNamedItem(oldNameAttr);
210             doc->addNamedItem(newNameAttr);
211         }
212         oldNameAttr = newNameAttr;
213     } else
214         HTMLElement::parseMappedAttribute(attr);
215 }
216
217 String HTMLImageElement::altText() const
218 {
219     // lets figure out the alt text.. magic stuff
220     // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
221     // also heavily discussed by Hixie on bugzilla
222     String alt = getAttribute(altAttr);
223     // fall back to title attribute
224     if (alt.isNull())
225         alt = getAttribute(titleAttr);
226     return alt;
227 }
228
229 RenderObject *HTMLImageElement::createRenderer(RenderArena *arena, RenderStyle *style)
230 {
231      return new (arena) RenderImage(this);
232 }
233
234 void HTMLImageElement::attach()
235 {
236     HTMLElement::attach();
237
238     if (RenderImage* imageObj = static_cast<RenderImage*>(renderer()))
239         imageObj->setCachedImage(m_imageLoader.image());
240 }
241
242 void HTMLImageElement::insertedIntoDocument()
243 {
244     Document* doc = document();
245     if (doc->isHTMLDocument())
246         static_cast<HTMLDocument*>(doc)->addNamedItem(oldNameAttr);
247
248     HTMLElement::insertedIntoDocument();
249 }
250
251 void HTMLImageElement::removedFromDocument()
252 {
253     Document* doc = document();
254     if (doc->isHTMLDocument())
255         static_cast<HTMLDocument*>(doc)->removeNamedItem(oldNameAttr);
256
257     HTMLElement::removedFromDocument();
258 }
259
260 int HTMLImageElement::width(bool ignorePendingStylesheets) const
261 {
262     if (!renderer()) {
263         // check the attribute first for an explicit pixel value
264         bool ok;
265         int width = getAttribute(widthAttr).toInt(&ok);
266         if (ok)
267             return width;
268         
269         // if the image is available, use its width
270         if (m_imageLoader.image())
271             return m_imageLoader.image()->imageSize().width();
272     }
273
274     Document* doc = document();
275     if (ignorePendingStylesheets)
276         doc->updateLayoutIgnorePendingStylesheets();
277     else
278         doc->updateLayout();
279
280     return renderer() ? renderer()->contentWidth() : 0;
281 }
282
283 int HTMLImageElement::height(bool ignorePendingStylesheets) const
284 {
285     if (!renderer()) {
286         // check the attribute first for an explicit pixel value
287         bool ok;
288         int height = getAttribute(heightAttr).toInt(&ok);
289         if (ok)
290             return height;
291         
292         // if the image is available, use its height
293         if (m_imageLoader.image())
294             return m_imageLoader.image()->imageSize().height();        
295     }
296
297     Document* doc = document();
298     if (ignorePendingStylesheets)
299         doc->updateLayoutIgnorePendingStylesheets();
300     else
301         doc->updateLayout();
302
303     return renderer() ? renderer()->contentHeight() : 0;
304 }
305
306 bool HTMLImageElement::isURLAttribute(Attribute *attr) const
307 {
308     return attr->name() == srcAttr || (attr->name() == usemapAttr && attr->value().domString()[0] != '#');
309 }
310
311 String HTMLImageElement::name() const
312 {
313     return getAttribute(nameAttr);
314 }
315
316 void HTMLImageElement::setName(const String& value)
317 {
318     setAttribute(nameAttr, value);
319 }
320
321 String HTMLImageElement::align() const
322 {
323     return getAttribute(alignAttr);
324 }
325
326 void HTMLImageElement::setAlign(const String& value)
327 {
328     setAttribute(alignAttr, value);
329 }
330
331 String HTMLImageElement::alt() const
332 {
333     return getAttribute(altAttr);
334 }
335
336 void HTMLImageElement::setAlt(const String& value)
337 {
338     setAttribute(altAttr, value);
339 }
340
341 int HTMLImageElement::border() const
342 {
343     // ### return value in pixels
344     return getAttribute(borderAttr).toInt();
345 }
346
347 void HTMLImageElement::setBorder(int value)
348 {
349     setAttribute(borderAttr, String::number(value));
350 }
351
352 void HTMLImageElement::setHeight(int value)
353 {
354     setAttribute(heightAttr, String::number(value));
355 }
356
357 int HTMLImageElement::hspace() const
358 {
359     // ### return actual value
360     return getAttribute(hspaceAttr).toInt();
361 }
362
363 void HTMLImageElement::setHspace(int value)
364 {
365     setAttribute(hspaceAttr, String::number(value));
366 }
367
368 bool HTMLImageElement::isMap() const
369 {
370     return !getAttribute(ismapAttr).isNull();
371 }
372
373 void HTMLImageElement::setIsMap(bool isMap)
374 {
375     setAttribute(ismapAttr, isMap ? "" : 0);
376 }
377
378 String HTMLImageElement::longDesc() const
379 {
380     return getAttribute(longdescAttr);
381 }
382
383 void HTMLImageElement::setLongDesc(const String& value)
384 {
385     setAttribute(longdescAttr, value);
386 }
387
388 String HTMLImageElement::src() const
389 {
390     return document()->completeURL(getAttribute(srcAttr));
391 }
392
393 void HTMLImageElement::setSrc(const String& value)
394 {
395     setAttribute(srcAttr, value);
396 }
397
398 String HTMLImageElement::useMap() const
399 {
400     return getAttribute(usemapAttr);
401 }
402
403 void HTMLImageElement::setUseMap(const String& value)
404 {
405     setAttribute(usemapAttr, value);
406 }
407
408 int HTMLImageElement::vspace() const
409 {
410     // ### return actual vspace
411     return getAttribute(vspaceAttr).toInt();
412 }
413
414 void HTMLImageElement::setVspace(int value)
415 {
416     setAttribute(vspaceAttr, String::number(value));
417 }
418
419 void HTMLImageElement::setWidth(int value)
420 {
421     setAttribute(widthAttr, String::number(value));
422 }
423
424 int HTMLImageElement::x() const
425 {
426     RenderObject *r = renderer();
427     if (!r)
428         return 0;
429     int x, y;
430     r->absolutePosition(x, y);
431     return x;
432 }
433
434 int HTMLImageElement::y() const
435 {
436     RenderObject *r = renderer();
437     if (!r)
438         return 0;
439     int x, y;
440     r->absolutePosition(x, y);
441     return y;
442 }
443
444 bool HTMLImageElement::complete() const
445 {
446     return m_imageLoader.imageComplete();
447 }
448
449 // -------------------------------------------------------------------------
450
451 HTMLMapElement::HTMLMapElement(Document *doc)
452     : HTMLElement(mapTag, doc)
453 {
454 }
455
456 HTMLMapElement::~HTMLMapElement()
457 {
458     document()->removeImageMap(this);
459 }
460
461 bool HTMLMapElement::checkDTD(const Node* newChild)
462 {
463     // FIXME: This seems really odd, allowing only blocks inside map elements.
464     return newChild->hasTagName(areaTag) || newChild->hasTagName(scriptTag) || inBlockTagList(newChild);
465 }
466
467 bool HTMLMapElement::mapMouseEvent(int x, int y, int width, int height, RenderObject::NodeInfo& info)
468 {
469     Node *node = this;
470     while ((node = node->traverseNextNode(this)))
471         if (node->hasTagName(areaTag))
472             if (static_cast<HTMLAreaElement *>(node)->mapMouseEvent(x, y, width, height, info))
473                 return true;
474     return false;
475 }
476
477 void HTMLMapElement::parseMappedAttribute(MappedAttribute *attr)
478 {
479     const QualifiedName& attrName = attr->name();
480     if (attrName == idAttr || attrName == nameAttr) {
481         Document* doc = document();
482         if (attrName == idAttr) {
483             // Call base class so that hasID bit gets set.
484             HTMLElement::parseMappedAttribute(attr);
485             if (doc->htmlMode() != Document::XHtml)
486                 return;
487         }
488         doc->removeImageMap(this);
489         m_name = attr->value();
490         if (m_name[0] == '#') {
491             String mapName = mapName.copy();
492             mapName.remove(0, 1);
493             m_name = mapName.impl();
494         }
495         doc->addImageMap(this);
496     } else
497         HTMLElement::parseMappedAttribute(attr);
498 }
499
500 PassRefPtr<HTMLCollection> HTMLMapElement::areas()
501 {
502     return new HTMLCollection(this, HTMLCollection::MAP_AREAS);
503 }
504
505 String HTMLMapElement::name() const
506 {
507     return getAttribute(nameAttr);
508 }
509
510 void HTMLMapElement::setName(const String& value)
511 {
512     setAttribute(nameAttr, value);
513 }
514
515 // -------------------------------------------------------------------------
516
517 HTMLAreaElement::HTMLAreaElement(Document *doc)
518     : HTMLAnchorElement(areaTag, doc)
519 {
520     m_coords = 0;
521     m_coordsLen = 0;
522     m_shape = Unknown;
523     lasth = -1;
524     lastw = -1;
525 }
526
527 HTMLAreaElement::~HTMLAreaElement()
528 {
529     delete [] m_coords;
530 }
531
532 void HTMLAreaElement::parseMappedAttribute(MappedAttribute *attr)
533 {
534     if (attr->name() == shapeAttr) {
535         if (equalIgnoringCase(attr->value(), "default"))
536             m_shape = Default;
537         else if (equalIgnoringCase(attr->value(), "circle"))
538             m_shape = Circle;
539         else if (equalIgnoringCase(attr->value(), "poly"))
540             m_shape = Poly;
541         else if (equalIgnoringCase(attr->value(), "rect"))
542             m_shape = Rect;
543     } else if (attr->name() == coordsAttr) {
544         delete [] m_coords;
545         m_coords = attr->value().toCoordsArray(m_coordsLen);
546     } else if (attr->name() == targetAttr) {
547         m_hasTarget = !attr->isNull();
548     } else if (attr->name() == altAttr || attr->name() == accesskeyAttr) {
549         // Do nothing.
550     } else
551         HTMLAnchorElement::parseMappedAttribute(attr);
552 }
553
554 bool HTMLAreaElement::mapMouseEvent(int x, int y, int width, int height, RenderObject::NodeInfo& info)
555 {
556     if (width != lastw || height != lasth) {
557         region = getRegion(width, height);
558         lastw = width;
559         lasth = height;
560     }
561
562     if (!region.contains(IntPoint(x, y)))
563         return false;
564     
565     info.setInnerNode(this);
566     info.setURLElement(this);
567     return true;
568 }
569
570 IntRect HTMLAreaElement::getRect(RenderObject* obj) const
571 {
572     int dx, dy;
573     obj->absolutePosition(dx, dy);
574     Path p = getRegion(lastw,lasth);
575     p.translate(dx, dy);
576     return p.boundingRect();
577 }
578
579 Path HTMLAreaElement::getRegion(int width, int height) const
580 {
581     if (!m_coords)
582         return Path();
583
584     // If element omits the shape attribute, select shape based on number of coordinates.
585     Shape shape = m_shape;
586     if (shape == Unknown) {
587         if (m_coordsLen == 3)
588             shape = Circle;
589         else if (m_coordsLen == 4)
590             shape = Rect;
591         else if (m_coordsLen >= 6)
592             shape = Poly;
593     }
594
595     switch (shape) {
596         case Poly:
597             if (m_coordsLen >= 6) {
598                 int numPoints = m_coordsLen / 2;
599                 IntPointArray points(numPoints);
600                 for (int i = 0; i < numPoints; ++i)
601                     points.setPoint(i, m_coords[i * 2].calcMinValue(width), m_coords[i * 2 + 1].calcMinValue(height));
602                 return Path(points);
603             }
604             break;
605         case Circle:
606             if (m_coordsLen >= 3) {
607                 Length radius = m_coords[2];
608                 int r = min(radius.calcMinValue(width), radius.calcMinValue(height));
609                 return Path(IntRect(m_coords[0].calcMinValue(width) - r, m_coords[1].calcMinValue(height) - r,
610                     2 * r, 2 * r), Path::Ellipse);
611             }
612             break;
613         case Rect:
614             if (m_coordsLen >= 4) {
615                 int x0 = m_coords[0].calcMinValue(width);
616                 int y0 = m_coords[1].calcMinValue(height);
617                 int x1 = m_coords[2].calcMinValue(width);
618                 int y1 = m_coords[3].calcMinValue(height);
619                 return Path(IntRect(x0, y0, x1 - x0, y1 - y0));
620             }
621             break;
622         case Default:
623             return Path(IntRect(0, 0, width, height));
624         case Unknown:
625             break;
626     }
627
628     return Path();
629 }
630
631 String HTMLAreaElement::accessKey() const
632 {
633     return getAttribute(accesskeyAttr);
634 }
635
636 void HTMLAreaElement::setAccessKey(const String& value)
637 {
638     setAttribute(accesskeyAttr, value);
639 }
640
641 String HTMLAreaElement::alt() const
642 {
643     return getAttribute(altAttr);
644 }
645
646 void HTMLAreaElement::setAlt(const String& value)
647 {
648     setAttribute(altAttr, value);
649 }
650
651 String HTMLAreaElement::coords() const
652 {
653     return getAttribute(coordsAttr);
654 }
655
656 void HTMLAreaElement::setCoords(const String& value)
657 {
658     setAttribute(coordsAttr, value);
659 }
660
661 String HTMLAreaElement::href() const
662 {
663     return document()->completeURL(getAttribute(hrefAttr));
664 }
665
666 void HTMLAreaElement::setHref(const String& value)
667 {
668     setAttribute(hrefAttr, value);
669 }
670
671 bool HTMLAreaElement::noHref() const
672 {
673     return !getAttribute(nohrefAttr).isNull();
674 }
675
676 void HTMLAreaElement::setNoHref(bool noHref)
677 {
678     setAttribute(nohrefAttr, noHref ? "" : 0);
679 }
680
681 String HTMLAreaElement::shape() const
682 {
683     return getAttribute(shapeAttr);
684 }
685
686 void HTMLAreaElement::setShape(const String& value)
687 {
688     setAttribute(shapeAttr, value);
689 }
690
691 int HTMLAreaElement::tabIndex() const
692 {
693     return getAttribute(tabindexAttr).toInt();
694 }
695
696 void HTMLAreaElement::setTabIndex(int tabIndex)
697 {
698     setAttribute(tabindexAttr, String::number(tabIndex));
699 }
700
701 String HTMLAreaElement::target() const
702 {
703     return getAttribute(targetAttr);
704 }
705
706 void HTMLAreaElement::setTarget(const String& value)
707 {
708     setAttribute(targetAttr, value);
709 }
710
711 }