f6f6e51aae4588cc64944fc89bb7426877f0a474
[WebKit-https.git] / WebCore / khtml / 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 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 "html/html_imageimpl.h"
25 #include "html/html_formimpl.h"
26 #include "html/html_documentimpl.h"
27
28 #include "misc/htmlhashes.h"
29 #include "khtmlview.h"
30 #include "khtml_part.h"
31
32 #include <kstringhandler.h>
33 #include <kglobal.h>
34 #include <kdebug.h>
35
36 #include "rendering/render_image.h"
37 #include "rendering/render_flow.h"
38 #include "css/cssstyleselector.h"
39 #include "css/cssproperties.h"
40 #include "css/cssvalues.h"
41 #include "css/csshelper.h"
42 #include "xml/dom2_eventsimpl.h"
43
44 #include <qstring.h>
45 #include <qpoint.h>
46 #include <qregion.h>
47 #include <qptrstack.h>
48 #include <qimage.h>
49 #include <qpointarray.h>
50
51 using namespace DOM;
52 using namespace khtml;
53
54 //#define INSTRUMENT_LAYOUT_SCHEDULING 1
55
56 HTMLImageLoader::HTMLImageLoader(ElementImpl* elt)
57 :m_element(elt), m_image(0), m_firedLoad(true), m_imageComplete(true)
58 {
59 }
60
61 HTMLImageLoader::~HTMLImageLoader()
62 {
63     if (m_image)
64         m_image->deref(this);
65     if (m_element->getDocument())
66         m_element->getDocument()->removeImage(this);
67 }
68
69 void HTMLImageLoader::updateFromElement()
70 {
71     // If we're not making renderers for the page, then don't load images.  We don't want to slow
72     // down the raw HTML parsing case by loading images we don't intend to display.
73     if (!element()->getDocument()->renderer())
74         return;
75
76     AtomicString attr;
77     if (element()->id() == ID_OBJECT)
78         attr = element()->getAttribute(ATTR_DATA);
79     else
80         attr = element()->getAttribute(ATTR_SRC);
81     
82     // Treat a lack of src or empty string for src as no image at all.
83     CachedImage* newImage = 0;
84     if (!attr.isEmpty())
85         newImage = element()->getDocument()->docLoader()->requestImage(khtml::parseURL(attr));
86
87     if (newImage != m_image) {
88         m_firedLoad = false;
89         m_imageComplete = false;
90         CachedImage* oldImage = m_image;
91         m_image = newImage;
92         if (m_image)
93             m_image->ref(this);
94         if (oldImage)
95             oldImage->deref(this);
96     }
97 #if APPLE_CHANGES
98     khtml::RenderImage *renderer = static_cast<khtml::RenderImage*>(element()->renderer());
99     if (renderer)
100         renderer->resetAnimation();
101 #endif
102 }
103
104 void HTMLImageLoader::dispatchLoadEvent()
105 {
106     if (!m_firedLoad) {
107         m_firedLoad = true;
108         if (m_image->isErrorImage())
109             element()->dispatchHTMLEvent(EventImpl::ERROR_EVENT, false, false);
110         else
111             element()->dispatchHTMLEvent(EventImpl::LOAD_EVENT, false, false);
112     }
113 }
114
115 void HTMLImageLoader::notifyFinished(CachedObject* image)
116 {
117     m_imageComplete = true;
118     DocumentImpl* document = element()->getDocument();
119     if (document) {
120         document->dispatchImageLoadEventSoon(this);
121 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
122         if (!document->ownerElement())
123             printf("Image loaded at %d\n", element()->getDocument()->elapsedTime());
124 #endif
125     }
126     if (element()->renderer()) {
127         RenderImage* imageObj = static_cast<RenderImage*>(element()->renderer());
128         imageObj->setImage(m_image);
129     }
130 }
131
132 // -------------------------------------------------------------------------
133
134 HTMLImageElementImpl::HTMLImageElementImpl(DocumentPtr *doc, HTMLFormElementImpl *f)
135     : HTMLElementImpl(doc), m_imageLoader(this), ismap(false), m_form(f)
136 {
137     if (m_form)
138         m_form->registerImgElement(this);
139 }
140
141 HTMLImageElementImpl::~HTMLImageElementImpl()
142 {
143     if (m_form)
144         m_form->removeImgElement(this);
145 }
146
147 NodeImpl::Id HTMLImageElementImpl::id() const
148 {
149     return ID_IMG;
150 }
151
152 bool HTMLImageElementImpl::mapToEntry(NodeImpl::Id attr, MappedAttributeEntry& result) const
153 {
154     switch(attr)
155     {
156         case ATTR_WIDTH:
157         case ATTR_HEIGHT:
158         case ATTR_VSPACE:
159         case ATTR_HSPACE:
160         case ATTR_VALIGN:
161             result = eUniversal;
162             return false;
163         case ATTR_BORDER:
164         case ATTR_ALIGN:
165             result = eReplaced; // Shared with embeds and iframes
166             return false;
167         default:
168             break;
169     }
170
171     return HTMLElementImpl::mapToEntry(attr, result);
172 }
173
174 void HTMLImageElementImpl::parseHTMLAttribute(HTMLAttributeImpl *attr)
175 {
176     switch (attr->id())
177     {
178     case ATTR_ALT:
179         if (m_render) static_cast<RenderImage*>(m_render)->updateAltText();
180         break;
181     case ATTR_SRC:
182         m_imageLoader.updateFromElement();
183         break;
184     case ATTR_WIDTH:
185         addCSSLength(attr, CSS_PROP_WIDTH, attr->value());
186         break;
187     case ATTR_HEIGHT:
188         addCSSLength(attr, CSS_PROP_HEIGHT, attr->value());
189         break;
190     case ATTR_BORDER:
191         // border="noborder" -> border="0"
192         if(attr->value().toInt()) {
193             addCSSLength(attr, CSS_PROP_BORDER_WIDTH, attr->value());
194             addCSSProperty(attr, CSS_PROP_BORDER_TOP_STYLE, CSS_VAL_SOLID);
195             addCSSProperty(attr, CSS_PROP_BORDER_RIGHT_STYLE, CSS_VAL_SOLID);
196             addCSSProperty(attr, CSS_PROP_BORDER_BOTTOM_STYLE, CSS_VAL_SOLID);
197             addCSSProperty(attr, CSS_PROP_BORDER_LEFT_STYLE, CSS_VAL_SOLID);
198         }
199         break;
200     case ATTR_VSPACE:
201         addCSSLength(attr, CSS_PROP_MARGIN_TOP, attr->value());
202         addCSSLength(attr, CSS_PROP_MARGIN_BOTTOM, attr->value());
203         break;
204     case ATTR_HSPACE:
205         addCSSLength(attr, CSS_PROP_MARGIN_LEFT, attr->value());
206         addCSSLength(attr, CSS_PROP_MARGIN_RIGHT, attr->value());
207         break;
208     case ATTR_ALIGN:
209         addHTMLAlignment(attr);
210         break;
211     case ATTR_VALIGN:
212         addCSSProperty(attr, CSS_PROP_VERTICAL_ALIGN, attr->value());
213         break;
214     case ATTR_USEMAP:
215         if ( attr->value().domString()[0] == '#' )
216             usemap = attr->value();
217         else {
218             QString url = getDocument()->completeURL( khtml::parseURL( attr->value() ).string() );
219             // ### we remove the part before the anchor and hope
220             // the map is on the same html page....
221             usemap = url;
222         }
223         m_hasAnchor = !attr->isNull();
224     case ATTR_ISMAP:
225         ismap = true;
226         break;
227     case ATTR_ONABORT: // ### add support for this
228         setHTMLEventListener(EventImpl::ABORT_EVENT,
229             getDocument()->createHTMLEventListener(attr->value().string(), this));
230         break;
231     case ATTR_ONERROR:
232         setHTMLEventListener(EventImpl::ERROR_EVENT,
233             getDocument()->createHTMLEventListener(attr->value().string(), this));
234         break;
235     case ATTR_ONLOAD:
236         setHTMLEventListener(EventImpl::LOAD_EVENT,
237             getDocument()->createHTMLEventListener(attr->value().string(), this));
238         break;
239     case ATTR_NOSAVE:
240         break;
241 #if APPLE_CHANGES
242     case ATTR_COMPOSITE:
243         _compositeOperator = attr->value().string();
244         break;
245 #endif
246     case ATTR_NAME:
247         {
248             QString newNameAttr = attr->value().string();
249             if (attached() && getDocument()->isHTMLDocument()) {
250                 HTMLDocumentImpl *document = static_cast<HTMLDocumentImpl *>(getDocument());
251                 document->removeNamedImageOrForm(oldNameAttr);
252                 document->addNamedImageOrForm(newNameAttr);
253             }
254             oldNameAttr = newNameAttr;
255         }
256         break;
257     case ATTR_ID:
258         {
259             QString newIdAttr = attr->value().string();
260             if (attached() && getDocument()->isHTMLDocument()) {
261                 HTMLDocumentImpl *document = static_cast<HTMLDocumentImpl *>(getDocument());
262                 document->removeNamedImageOrForm(oldIdAttr);
263                 document->addNamedImageOrForm(newIdAttr);
264             }
265             oldIdAttr = newIdAttr;
266         }
267         // fall through
268     default:
269         HTMLElementImpl::parseHTMLAttribute(attr);
270     }
271 }
272
273 DOMString HTMLImageElementImpl::altText() const
274 {
275     // lets figure out the alt text.. magic stuff
276     // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
277     // also heavily discussed by Hixie on bugzilla
278     DOMString alt( getAttribute( ATTR_ALT ) );
279     // fall back to title attribute
280     if ( alt.isNull() )
281         alt = getAttribute( ATTR_TITLE );
282 #if 0
283     if ( alt.isNull() ) {
284         QString p = KURL( getDocument()->completeURL( getAttribute(ATTR_SRC).string() ) ).prettyURL();
285         int pos;
286         if ( ( pos = p.findRev( '.' ) ) > 0 )
287             p.truncate( pos );
288         alt = DOMString( KStringHandler::csqueeze( p ) );
289     }
290 #endif
291
292     return alt;
293 }
294
295 RenderObject *HTMLImageElementImpl::createRenderer(RenderArena *arena, RenderStyle *style)
296 {
297      return new (arena) RenderImage(this);
298 }
299
300 void HTMLImageElementImpl::attach()
301 {
302     HTMLElementImpl::attach();
303
304     if (renderer()) {
305         RenderImage* imageObj = static_cast<RenderImage*>(renderer());
306         imageObj->setImage(m_imageLoader.image());
307     }
308
309     if (getDocument()->isHTMLDocument()) {
310         HTMLDocumentImpl *document = static_cast<HTMLDocumentImpl *>(getDocument());
311         document->addNamedImageOrForm(oldIdAttr);
312         document->addNamedImageOrForm(oldNameAttr);
313     }
314 }
315
316 void HTMLImageElementImpl::detach()
317 {
318     if (getDocument()->isHTMLDocument()) {
319         HTMLDocumentImpl *document = static_cast<HTMLDocumentImpl *>(getDocument());
320         document->removeNamedImageOrForm(oldIdAttr);
321         document->removeNamedImageOrForm(oldNameAttr);
322     }
323
324     HTMLElementImpl::detach();
325 }
326
327 long HTMLImageElementImpl::width(bool ignorePendingStylesheets) const
328 {
329     if (!m_render) {
330         // check the attribute first for an explicit pixel value
331         DOM::DOMString attrWidth = getAttribute(ATTR_WIDTH);
332         bool ok;
333         long width = attrWidth.string().toLong(&ok);
334         if (ok) {
335           return width;
336         }
337     }
338
339     DOM::DocumentImpl* docimpl = getDocument();
340     if (docimpl) {
341         if (ignorePendingStylesheets)
342             docimpl->updateLayoutIgnorePendingStylesheets();
343         else
344             docimpl->updateLayout();
345     }
346
347     if (!m_render) {
348         return 0;
349     }
350
351     return m_render->contentWidth();
352 }
353
354 long HTMLImageElementImpl::height(bool ignorePendingStylesheets) const
355 {
356     if (!m_render) {
357         // check the attribute first for an explicit pixel value
358         DOM::DOMString attrHeight = getAttribute(ATTR_HEIGHT);
359         bool ok;
360         long height = attrHeight.string().toLong(&ok);
361         if (ok) {
362           return height;
363         }
364     }
365
366     DOM::DocumentImpl* docimpl = getDocument();
367     if (docimpl) {
368         if (ignorePendingStylesheets)
369             docimpl->updateLayoutIgnorePendingStylesheets();
370         else
371             docimpl->updateLayout();
372     }
373
374     if (!m_render) {
375         return 0;
376     }
377
378     return m_render->contentHeight();
379 }
380
381 QImage HTMLImageElementImpl::currentImage() const
382 {
383     RenderImage *r = static_cast<RenderImage*>(renderer());
384     if (r)
385         return r->pixmap().convertToImage();
386     return QImage();
387 }
388
389 bool HTMLImageElementImpl::isURLAttribute(AttributeImpl *attr) const
390 {
391     return (attr->id() == ATTR_SRC || (attr->id() == ATTR_USEMAP && attr->value().domString()[0] != '#'));
392 }
393
394 // -------------------------------------------------------------------------
395
396 HTMLMapElementImpl::HTMLMapElementImpl(DocumentPtr *doc)
397     : HTMLElementImpl(doc)
398 {
399 }
400
401 HTMLMapElementImpl::~HTMLMapElementImpl()
402 {
403     if (getDocument())
404         getDocument()->removeImageMap(this);
405 }
406
407 NodeImpl::Id HTMLMapElementImpl::id() const
408 {
409     return ID_MAP;
410 }
411
412 bool
413 HTMLMapElementImpl::mapMouseEvent(int x_, int y_, int width_, int height_,
414                                   RenderObject::NodeInfo& info)
415 {
416     //cout << "map:mapMouseEvent " << endl;
417     //cout << x_ << " " << y_ <<" "<< width_ <<" "<< height_ << endl;
418     QPtrStack<NodeImpl> nodeStack;
419
420     NodeImpl *current = firstChild();
421     while(1)
422     {
423         if(!current)
424         {
425             if(nodeStack.isEmpty()) break;
426             current = nodeStack.pop();
427             current = current->nextSibling();
428             continue;
429         }
430         if(current->id()==ID_AREA)
431         {
432             //cout << "area found " << endl;
433             HTMLAreaElementImpl* area=static_cast<HTMLAreaElementImpl*>(current);
434             if (area->mapMouseEvent(x_,y_,width_,height_, info))
435                 return true;
436         }
437         NodeImpl *child = current->firstChild();
438         if(child)
439         {
440             nodeStack.push(current);
441             current = child;
442         }
443         else
444         {
445             current = current->nextSibling();
446         }
447     }
448
449     return false;
450 }
451
452 void HTMLMapElementImpl::parseHTMLAttribute(HTMLAttributeImpl *attr)
453 {
454     switch (attr->id())
455     {
456     case ATTR_ID:
457         // Must call base class so that hasID bit gets set.
458         HTMLElementImpl::parseHTMLAttribute(attr);
459         if (getDocument()->htmlMode() != DocumentImpl::XHtml) break;
460         // fall through
461     case ATTR_NAME:
462         getDocument()->removeImageMap(this);
463         name = attr->value();
464         if (name.length() != 0 && name[0] == '#')
465             name.remove(0, 1);
466         getDocument()->addImageMap(this);
467         break;
468     default:
469         HTMLElementImpl::parseHTMLAttribute(attr);
470     }
471 }
472
473 // -------------------------------------------------------------------------
474
475 HTMLAreaElementImpl::HTMLAreaElementImpl(DocumentPtr *doc)
476     : HTMLAnchorElementImpl(doc)
477 {
478     m_coords=0;
479     m_coordsLen = 0;
480     shape = Unknown;
481     lasth = lastw = -1;
482 }
483
484 HTMLAreaElementImpl::~HTMLAreaElementImpl()
485 {
486     if (m_coords) delete [] m_coords;
487 }
488
489 NodeImpl::Id HTMLAreaElementImpl::id() const
490 {
491     return ID_AREA;
492 }
493
494 void HTMLAreaElementImpl::parseHTMLAttribute(HTMLAttributeImpl *attr)
495 {
496     switch (attr->id())
497     {
498     case ATTR_SHAPE:
499         if ( strcasecmp( attr->value(), "default" ) == 0 )
500             shape = Default;
501         else if ( strcasecmp( attr->value(), "circle" ) == 0 )
502             shape = Circle;
503         else if ( strcasecmp( attr->value(), "poly" ) == 0 )
504             shape = Poly;
505         else if ( strcasecmp( attr->value(), "rect" ) == 0 )
506             shape = Rect;
507         break;
508     case ATTR_COORDS:
509         if (m_coords) delete [] m_coords;
510         m_coords = attr->value().toLengthArray(m_coordsLen);
511         break;
512     case ATTR_TARGET:
513         m_hasTarget = !attr->isNull();
514         break;
515     case ATTR_ALT:
516         break;
517     case ATTR_ACCESSKEY:
518         break;
519     default:
520         HTMLAnchorElementImpl::parseHTMLAttribute(attr);
521     }
522 }
523
524 bool HTMLAreaElementImpl::mapMouseEvent(int x_, int y_, int width_, int height_,
525                                    RenderObject::NodeInfo& info)
526 {
527     bool inside = false;
528     if (width_ != lastw || height_ != lasth)
529     {
530         region=getRegion(width_, height_);
531         lastw=width_; lasth=height_;
532     }
533     if (region.contains(QPoint(x_,y_)))
534     {
535         inside = true;
536         info.setInnerNode(this);
537         info.setURLElement(this);
538     }
539
540     return inside;
541 }
542
543 QRect HTMLAreaElementImpl::getRect(RenderObject* obj) const
544 {
545     int dx, dy;
546     obj->absolutePosition(dx, dy);
547     QRegion region = getRegion(lastw,lasth);
548     region.translate(dx, dy);
549     return region.boundingRect();
550 }
551
552 QRegion HTMLAreaElementImpl::getRegion(int width_, int height_) const
553 {
554     QRegion region;
555     if (!m_coords)
556         return region;
557
558     // added broken HTML support (Dirk): some pages omit the SHAPE
559     // attribute, so we try to guess by looking at the coords count
560     // what the HTML author tried to tell us.
561
562     // a Poly needs at least 3 points (6 coords), so this is correct
563     if ((shape==Poly || shape==Unknown) && m_coordsLen > 5) {
564         // make sure its even
565         int len = m_coordsLen >> 1;
566         QPointArray points(len);
567         for (int i = 0; i < len; ++i)
568             points.setPoint(i, m_coords[(i<<1)].minWidth(width_),
569                             m_coords[(i<<1)+1].minWidth(height_));
570         region = QRegion(points);
571     }
572     else if (shape==Circle && m_coordsLen>=3 || shape==Unknown && m_coordsLen == 3) {
573         int r = kMin(m_coords[2].minWidth(width_), m_coords[2].minWidth(height_));
574         region = QRegion(m_coords[0].minWidth(width_)-r,
575                          m_coords[1].minWidth(height_)-r, 2*r, 2*r,QRegion::Ellipse);
576     }
577     else if (shape==Rect && m_coordsLen>=4 || shape==Unknown && m_coordsLen == 4) {
578         int x0 = m_coords[0].minWidth(width_);
579         int y0 = m_coords[1].minWidth(height_);
580         int x1 = m_coords[2].minWidth(width_);
581         int y1 = m_coords[3].minWidth(height_);
582         region = QRegion(x0,y0,x1-x0,y1-y0);
583     }
584     else if (shape==Default)
585         region = QRegion(0,0,width_,height_);
586     // else
587        // return null region
588
589     return region;
590 }