Reviewed by Darin
[WebKit-https.git] / WebCore / khtml / rendering / render_replaced.cpp
1 /**
2  * This file is part of the HTML widget for KDE.
3  *
4  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5  * Copyright (C) 2000 Dirk Mueller (mueller@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 "render_replaced.h"
25
26 #include "render_arena.h"
27 #include "render_canvas.h"
28 #include "render_line.h"
29
30 #include <assert.h>
31 #include <qwidget.h>
32 #include <qpainter.h>
33 #include <qevent.h>
34 #include <qapplication.h>
35
36 #include "khtml_ext.h"
37 #include "khtmlview.h"
38 #include "xml/dom2_eventsimpl.h"
39 #include "khtml_part.h"
40 #include "xml/dom_docimpl.h" // ### remove dependency
41 #include "xml/dom_position.h"
42 #include <kdebug.h>
43
44 using namespace khtml;
45 using namespace DOM;
46
47
48 RenderReplaced::RenderReplaced(DOM::NodeImpl* node)
49     : RenderBox(node)
50 {
51     // init RenderObject attributes
52     setReplaced(true);
53
54     m_intrinsicWidth = 200;
55     m_intrinsicHeight = 150;
56 }
57
58 bool RenderReplaced::shouldPaint(PaintInfo& i, int& _tx, int& _ty)
59 {
60     if (i.phase != PaintActionForeground && i.phase != PaintActionOutline && i.phase != PaintActionSelection)
61         return false;
62
63     if (!shouldPaintWithinRoot(i))
64         return false;
65         
66     // if we're invisible or haven't received a layout yet, then just bail.
67     if (style()->visibility() != VISIBLE || m_y <=  -500000)  return false;
68
69     int tx = _tx + m_x;
70     int ty = _ty + m_y;
71
72     // Early exit if the element touches the edges.
73     int os = 2*maximalOutlineSize(i.phase);
74     if ((tx >= i.r.x() + i.r.width() + os) || (tx + m_width <= i.r.x() - os))
75         return false;
76     if ((ty >= i.r.y() + i.r.height() + os) || (ty + m_height <= i.r.y() - os))
77         return false;
78
79     return true;
80 }
81
82 void RenderReplaced::calcMinMaxWidth()
83 {
84     KHTMLAssert( !minMaxKnown());
85
86 #ifdef DEBUG_LAYOUT
87     kdDebug( 6040 ) << "RenderReplaced::calcMinMaxWidth() known=" << minMaxKnown() << endl;
88 #endif
89
90     int width = calcReplacedWidth() + paddingLeft() + paddingRight() + borderLeft() + borderRight();
91     if (style()->width().isPercent() || (style()->width().isVariable() && style()->height().isPercent())) {
92         m_minWidth = 0;
93         m_maxWidth = width;
94     }
95     else
96         m_minWidth = m_maxWidth = width;
97
98     setMinMaxKnown();
99 }
100
101 short RenderReplaced::lineHeight( bool, bool ) const
102 {
103     return height()+marginTop()+marginBottom();
104 }
105
106 short RenderReplaced::baselinePosition( bool, bool ) const
107 {
108     return height()+marginTop()+marginBottom();
109 }
110
111 long RenderReplaced::caretMinOffset() const 
112
113     return 0; 
114 }
115
116 // Returns 1 since a replaced element can have the caret positioned 
117 // at its beginning (0), or at its end (1).
118 long RenderReplaced::caretMaxOffset() const 
119
120     return 1; 
121 }
122
123 unsigned long RenderReplaced::caretMaxRenderedOffset() const
124 {
125     return 1; 
126 }
127
128 Position RenderReplaced::positionForCoordinates(int _x, int _y)
129 {
130     InlineBox *box = inlineBoxWrapper();
131     if (!box)
132         return Position(element(), 0);
133
134     RootInlineBox *root = box->root();
135
136     int absx, absy;
137     containingBlock()->absolutePosition(absx, absy);
138
139     int top = absy + root->topOverflow();
140     int bottom = root->nextRootBox() ? absy + root->nextRootBox()->topOverflow() : absy + root->bottomOverflow();
141
142     if (_y < top)
143         return Position(element(), caretMinOffset()); // coordinates are above
144     
145     if (_y >= bottom)
146         return Position(element(), caretMaxOffset()); // coordinates are below
147     
148     if (element()) {
149         if (_x <= absx + xPos() + (width() / 2))
150             return Position(element(), 0);
151         return Position(element(), 1);
152     }
153
154     return RenderBox::positionForCoordinates(_x, _y);
155 }
156
157 // -----------------------------------------------------------------------------
158
159 RenderWidget::RenderWidget(DOM::NodeImpl* node)
160       : RenderReplaced(node),
161         m_deleteWidget(false)
162 {
163     m_widget = 0;
164     // a replaced element doesn't support being anonymous
165     assert(node);
166     m_view = node->getDocument()->view();
167
168     // this is no real reference counting, its just there
169     // to make sure that we're not deleted while we're recursed
170     // in an eventFilter of the widget
171     ref();
172 }
173
174 void RenderWidget::detach()
175 {
176     remove();
177
178     if ( m_widget ) {
179         if ( m_view )
180             m_view->removeChild( m_widget );
181
182         m_widget->removeEventFilter( this );
183         m_widget->setMouseTracking( false );
184     }
185
186     RenderArena* arena = renderArena();
187     if (m_inlineBoxWrapper) {
188         if (!documentBeingDestroyed())
189             m_inlineBoxWrapper->remove();
190         m_inlineBoxWrapper->detach(arena);
191         m_inlineBoxWrapper = 0;
192     }
193     setNode(0);
194     deref(arena);
195 }
196
197 RenderWidget::~RenderWidget()
198 {
199     KHTMLAssert( refCount() <= 0 );
200
201     if (m_deleteWidget) {
202         delete m_widget;
203     }
204 }
205
206 void  RenderWidget::resizeWidget( QWidget *widget, int w, int h )
207 {
208 #if !APPLE_CHANGES
209     // ugly hack to limit the maximum size of the widget (as X11 has problems if it's bigger)
210     h = QMIN( h, 3072 );
211     w = QMIN( w, 2000 );
212 #endif
213
214     if (element() && (widget->width() != w || widget->height() != h)) {
215         RenderArena *arena = ref();
216         element()->ref();
217         widget->resize( w, h );
218         element()->deref();
219         deref(arena);
220     }
221 }
222
223 void RenderWidget::setQWidget(QWidget *widget, bool deleteWidget)
224 {
225     if (widget != m_widget)
226     {
227         if (m_widget) {
228             m_widget->removeEventFilter(this);
229             disconnect( m_widget, SIGNAL( destroyed()), this, SLOT( slotWidgetDestructed()));
230             if (m_deleteWidget) {
231                 delete m_widget;
232             }
233             m_widget = 0;
234         }
235         m_widget = widget;
236         if (m_widget) {
237             connect( m_widget, SIGNAL( destroyed()), this, SLOT( slotWidgetDestructed()));
238             m_widget->installEventFilter(this);
239             // if we've already received a layout, apply the calculated space to the
240             // widget immediately, but we have to have really been full constructed (with a non-null
241             // style pointer).
242             if (!needsLayout() && style()) {
243                 resizeWidget( m_widget,
244                               m_width-borderLeft()-borderRight()-paddingLeft()-paddingRight(),
245                               m_height-borderLeft()-borderRight()-paddingLeft()-paddingRight() );
246             }
247             else
248                 setPos(xPos(), -500000);
249
250 #if APPLE_CHANGES
251             if (style()) {
252                 if (style()->visibility() != VISIBLE)
253                     m_widget->hide();
254                 else
255                     m_widget->show();
256             }
257 #endif
258         }
259         m_view->addChild( m_widget, -500000, 0 );
260     }
261     m_deleteWidget = deleteWidget;
262 }
263
264 void RenderWidget::layout( )
265 {
266     KHTMLAssert( needsLayout() );
267     KHTMLAssert( minMaxKnown() );
268 #if !APPLE_CHANGES
269     if ( m_widget ) {
270         resizeWidget( m_widget,
271                       m_width-borderLeft()-borderRight()-paddingLeft()-paddingRight(),
272                       m_height-borderLeft()-borderRight()-paddingLeft()-paddingRight() );
273     }
274 #endif
275
276     setNeedsLayout(false);
277 }
278
279 #if APPLE_CHANGES
280 void RenderWidget::sendConsumedMouseUp(const QPoint &mousePos, int button, int state)
281 {
282     RenderArena *arena = ref();
283     QMouseEvent e( QEvent::MouseButtonRelease, mousePos, button, state);
284
285     element()->dispatchMouseEvent(&e, EventImpl::MOUSEUP_EVENT, 0);
286     deref(arena);
287 }
288 #endif
289
290 void RenderWidget::slotWidgetDestructed()
291 {
292     m_widget = 0;
293 }
294
295 void RenderWidget::setStyle(RenderStyle *_style)
296 {
297     RenderReplaced::setStyle(_style);
298     if (m_widget)
299     {
300         m_widget->setFont(style()->font());
301         if (style()->visibility() != VISIBLE)
302             m_widget->hide();
303 #if APPLE_CHANGES
304         else
305             m_widget->show();
306 #endif
307     }
308 }
309
310 void RenderWidget::paint(PaintInfo& i, int _tx, int _ty)
311 {
312     if (!shouldPaint(i, _tx, _ty)) return;
313
314     _tx += m_x;
315     _ty += m_y;
316     
317     if (shouldPaintBackgroundOrBorder() && i.phase != PaintActionOutline) 
318         paintBoxDecorations(i, _tx, _ty);
319
320 #if APPLE_CHANGES
321     if (!m_widget || !m_view || i.phase != PaintActionForeground ||
322         style()->visibility() != VISIBLE)
323         return;
324
325     // Move the widget if necessary.  We normally move and resize widgets during layout, but sometimes
326     // widgets can move without layout occurring (most notably when you scroll a document that
327     // contains fixed positioned elements).
328     int newX = _tx + borderLeft() + paddingLeft();
329     int newY = _ty + borderTop() + paddingTop();
330     if (m_view->childX(m_widget) != newX || m_view->childY(m_widget) != newY)
331         m_widget->move(newX, newY);
332     
333     // Tell the widget to paint now.  This is the only time the widget is allowed
334     // to paint itself.  That way it will composite properly with z-indexed layers.
335     m_widget->paint(i.p, i.r);
336     
337 #else
338     if (!m_widget || !m_view || i.phase != PaintActionForeground)
339         return;
340     
341     if (style()->visibility() != VISIBLE) {
342         m_widget->hide();
343         return;
344     }
345
346     int xPos = _tx+borderLeft()+paddingLeft();
347     int yPos = _ty+borderTop()+paddingTop();
348
349     int childw = m_widget->width();
350     int childh = m_widget->height();
351     if ( (childw == 2000 || childh == 3072) && m_widget->inherits( "KHTMLView" ) ) {
352         KHTMLView *vw = static_cast<KHTMLView *>(m_widget);
353         int cy = m_view->contentsY();
354         int ch = m_view->visibleHeight();
355
356
357         int childx = m_view->childX( m_widget );
358         int childy = m_view->childY( m_widget );
359
360         int xNew = xPos;
361         int yNew = childy;
362
363         //      qDebug("cy=%d, ch=%d, childy=%d, childh=%d", cy, ch, childy, childh );
364         if ( childh == 3072 ) {
365             if ( cy + ch > childy + childh ) {
366                 yNew = cy + ( ch - childh )/2;
367             } else if ( cy < childy ) {
368                 yNew = cy + ( ch - childh )/2;
369             }
370 //          qDebug("calculated yNew=%d", yNew);
371         }
372         yNew = kMin( yNew, yPos + m_height - childh );
373         yNew = kMax( yNew, yPos );
374         if ( yNew != childy || xNew != childx ) {
375             if ( vw->contentsHeight() < yNew - yPos + childh )
376                 vw->resizeContents( vw->contentsWidth(), yNew - yPos + childh );
377             vw->setContentsPos( xNew - xPos, yNew - yPos );
378         }
379         xPos = xNew;
380         yPos = yNew;
381     }
382
383     m_view->addChild(m_widget, xPos, yPos );
384     m_widget->show();
385 #endif
386 }
387
388 void RenderWidget::handleFocusOut()
389 {
390 }
391
392 bool RenderWidget::eventFilter(QObject* /*o*/, QEvent* e)
393 {
394     if ( !element() ) return true;
395
396     RenderArena *arena = ref();
397     DOM::NodeImpl *elem = element();
398     elem->ref();
399
400     bool filtered = false;
401
402     //kdDebug() << "RenderWidget::eventFilter type=" << e->type() << endl;
403     switch(e->type()) {
404     case QEvent::FocusOut:
405        //static const char* const r[] = {"Mouse", "Tab", "Backtab", "ActiveWindow", "Popup", "Shortcut", "Other" };
406         //kdDebug() << "RenderFormElement::eventFilter FocusOut widget=" << m_widget << " reason:" << r[QFocusEvent::reason()] << endl;
407         // Don't count popup as a valid reason for losing the focus
408         // (example: opening the options of a select combobox shouldn't emit onblur)
409         if ( QFocusEvent::reason() != QFocusEvent::Popup )
410        {
411            //kdDebug(6000) << "RenderWidget::eventFilter captures FocusOut" << endl;
412             if (elem->getDocument()->focusNode() == elem)
413                 elem->getDocument()->setFocusNode(0);
414 //             if (  elem->isEditable() ) {
415 //                 KHTMLPartBrowserExtension *ext = static_cast<KHTMLPartBrowserExtension *>( elem->view->part()->browserExtension() );
416 //                 if ( ext )  ext->editableWidgetBlurred( m_widget );
417 //             }
418             handleFocusOut();
419         }
420         break;
421     case QEvent::FocusIn:
422         //kdDebug(6000) << "RenderWidget::eventFilter captures FocusIn" << endl;
423         elem->getDocument()->setFocusNode(elem);
424 //         if ( isEditable() ) {
425 //             KHTMLPartBrowserExtension *ext = static_cast<KHTMLPartBrowserExtension *>( elem->view->part()->browserExtension() );
426 //             if ( ext )  ext->editableWidgetFocused( m_widget );
427 //         }
428         break;
429     case QEvent::MouseButtonPress:
430 //       handleMousePressed(static_cast<QMouseEvent*>(e));
431         break;
432     case QEvent::MouseButtonRelease:
433 //    {
434 //         int absX, absY;
435 //         absolutePosition(absX,absY);
436 //         QMouseEvent* _e = static_cast<QMouseEvent*>(e);
437 //         m_button = _e->button();
438 //         m_state  = _e->state();
439 //         QMouseEvent e2(e->type(),QPoint(absX,absY)+_e->pos(),_e->button(),_e->state());
440
441 //         elem->dispatchMouseEvent(&e2,EventImpl::MOUSEUP_EVENT,m_clickCount);
442
443 //         if((m_mousePos - e2.pos()).manhattanLength() <= QApplication::startDragDistance()) {
444 //             // DOM2 Events section 1.6.2 says that a click is if the mouse was pressed
445 //             // and released in the "same screen location"
446 //             // As people usually can't click on the same pixel, we're a bit tolerant here
447 //             elem->dispatchMouseEvent(&e2,EventImpl::CLICK_EVENT,m_clickCount);
448 //         }
449
450 //         if(!isRenderButton()) {
451 //             // ### DOMActivate is also dispatched for thigs like selects & textareas -
452 //             // not sure if this is correct
453 //             elem->dispatchUIEvent(EventImpl::DOMACTIVATE_EVENT,m_isDoubleClick ? 2 : 1);
454 //             elem->dispatchMouseEvent(&e2, m_isDoubleClick ? EventImpl::KHTML_DBLCLICK_EVENT : EventImpl::KHTML_CLICK_EVENT, m_clickCount);
455 //             m_isDoubleClick = false;
456 //         }
457 //         else
458 //             // save position for slotClicked - see below -
459 //             m_mousePos = e2.pos();
460 //     }
461     break;
462     case QEvent::MouseButtonDblClick:
463 //     {
464 //         m_isDoubleClick = true;
465 //         handleMousePressed(static_cast<QMouseEvent*>(e));
466 //     }
467     break;
468     case QEvent::MouseMove:
469 //     {
470 //         int absX, absY;
471 //         absolutePosition(absX,absY);
472 //         QMouseEvent* _e = static_cast<QMouseEvent*>(e);
473 //         QMouseEvent e2(e->type(),QPoint(absX,absY)+_e->pos(),_e->button(),_e->state());
474 //         elem->dispatchMouseEvent(&e2);
475 //         // ### change cursor like in KHTMLView?
476 //     }
477     break;
478     case QEvent::KeyPress:
479     case QEvent::KeyRelease:
480     {
481         if (!elem->dispatchKeyEvent(static_cast<QKeyEvent*>(e)))
482             filtered = true;
483         break;
484     }
485     default: break;
486     };
487
488     elem->deref();
489
490     // stop processing if the widget gets deleted, but continue in all other cases
491     if (hasOneRef())
492         filtered = true;
493     deref(arena);
494
495     return filtered;
496 }
497
498 void RenderWidget::deref(RenderArena *arena)
499 {
500     if (_ref) _ref--; 
501     if (!_ref)
502         arenaDelete(arena);
503 }
504
505 #if APPLE_CHANGES
506 void RenderWidget::updateWidgetPositions()
507 {
508     if (!m_widget)
509         return;
510     
511     int x, y, width, height;
512     absolutePosition(x,y);
513     x += borderLeft() + paddingLeft();
514     y += borderTop() + paddingTop();
515     width = m_width - borderLeft() - borderRight() - paddingLeft() - paddingRight();
516     height = m_height - borderTop() - borderBottom() - paddingTop() - paddingBottom();
517     QRect newBounds(x,y,width,height);
518     QRect oldBounds(m_widget->frameGeometry());
519     if (newBounds != oldBounds) {
520         // The widget changed positions.  Update the frame geometry.
521         if (checkForRepaintDuringLayout()) {
522             RenderCanvas* c = canvas();
523             if (!c->printingMode()) {
524                 c->repaintViewRectangle(oldBounds);
525                 c->repaintViewRectangle(newBounds);
526             }
527         }
528
529         RenderArena *arena = ref();
530         element()->ref();
531         m_widget->setFrameGeometry(newBounds);
532         element()->deref();
533         deref(arena);
534     }
535 }
536 #endif
537
538 void RenderWidget::setSelectionState(SelectionState s) 
539 {
540     if (m_selectionState != s) {
541         m_selectionState = s;
542         if (m_widget) {
543             m_widget->setIsSelected(m_selectionState != SelectionNone);
544         }
545     }
546 }
547
548 #include "render_replaced.moc"