fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / khtml / khtmlview.cpp
blob8062dd421df5a867dbb5138210afc053c9a3275f
1 /* This file is part of the KDE project
3 * Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
4 * 1999 Lars Knoll <knoll@kde.org>
5 * 1999 Antti Koivisto <koivisto@kde.org>
6 * 2000-2004 Dirk Mueller <mueller@kde.org>
7 * 2003 Leo Savernik <l.savernik@aon.at>
8 * 2003-2008 Apple Computer, Inc.
9 * 2008 Allan Sandfeld Jensen <kde@carewolf.com>
10 * 2006-2008 Germain Garand <germain@ebooksfrance.org>
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Library General Public
14 * License as published by the Free Software Foundation; either
15 * version 2 of the License, or (at your option) any later version.
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Library General Public License for more details.
22 * You should have received a copy of the GNU Library General Public License
23 * along with this library; see the file COPYING.LIB. If not, write to
24 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 * Boston, MA 02110-1301, USA.
29 #include "khtmlview.h"
31 #include "khtmlview.moc"
33 #include "khtml_part.h"
34 #include "khtml_events.h"
35 #ifdef Q_WS_X11
36 #include <qx11info_x11.h>
37 #endif
39 #include "html/html_documentimpl.h"
40 #include "html/html_inlineimpl.h"
41 #include "html/html_formimpl.h"
42 #include "html/htmltokenizer.h"
43 #include "editing/editor.h"
44 #include "rendering/render_arena.h"
45 #include "rendering/render_canvas.h"
46 #include "rendering/render_frames.h"
47 #include "rendering/render_replaced.h"
48 #include "rendering/render_form.h"
49 #include "rendering/render_layer.h"
50 #include "rendering/render_line.h"
51 #include "rendering/render_table.h"
52 // removeme
53 #define protected public
54 #include "rendering/render_text.h"
55 #undef protected
56 #include "xml/dom2_eventsimpl.h"
57 #include "css/cssstyleselector.h"
58 #include "css/csshelper.h"
59 #include "misc/htmlhashes.h"
60 #include "misc/helper.h"
61 #include "misc/loader.h"
62 #include "khtml_settings.h"
63 #include "khtml_printsettings.h"
65 #include "khtmlpart_p.h"
67 #include <kcursor.h>
68 #include <kdebug.h>
69 #include <kglobalsettings.h>
70 #include <kdialog.h>
71 #include <kiconloader.h>
72 #include <klocale.h>
73 #include <knotification.h>
74 #include <kdeprintdialog.h>
75 #include <kconfig.h>
76 #include <kstandarddirs.h>
77 #include <kstandardshortcut.h>
78 #include <kstringhandler.h>
79 #include <kconfiggroup.h>
81 #include <QtGui/QBitmap>
82 #include <QtGui/QLabel>
83 #include <QtCore/QObject>
84 #include <QtGui/QPainter>
85 #include <QtCore/QHash>
86 #include <QtGui/QToolTip>
87 #include <QtCore/QString>
88 #include <QtGui/QTextDocument>
89 #include <QtCore/QTimer>
90 #include <QtCore/QAbstractEventDispatcher>
91 #include <QtCore/QVector>
92 #include <QtGui/QAbstractScrollArea>
93 #include <QtGui/QPrinter>
94 #include <QtGui/QPrintDialog>
96 //#define DEBUG_FLICKER
98 #include <limits.h>
99 #ifdef Q_WS_X11
100 #include <X11/Xlib.h>
101 #include <fixx11h.h>
102 #elif defined(Q_WS_WIN)
103 #include <windows.h>
104 #endif
106 #if 0
107 namespace khtml {
108 void dumpLineBoxes(RenderFlow *flow);
110 #endif
112 using namespace DOM;
113 using namespace khtml;
115 #ifndef NDEBUG
116 static const int sFirstLayoutDelay = 760;
117 static const int sParsingLayoutsInterval = 420;
118 static const int sLayoutAttemptDelay = 400;
119 #else
120 static const int sFirstLayoutDelay = 540;
121 static const int sParsingLayoutsInterval = 360;
122 static const int sLayoutAttemptDelay = 340;
123 #endif
124 static const int sLayoutAttemptIncrement = 20;
125 static const int sParsingLayoutsIncrement = 60;
127 static const int sSmoothScrollTime = 140;
128 static const int sSmoothScrollTick = 14;
129 static const int sSmoothScrollMinStaticPixels = 320*200;
131 class KHTMLViewPrivate {
132 friend class KHTMLView;
133 public:
135 enum PseudoFocusNodes {
136 PFNone,
137 PFTop,
138 PFBottom
141 enum StaticBackgroundState {
142 SBNone = 0,
143 SBPartial,
144 SBFull
147 enum CompletedState {
148 CSNone = 0,
149 CSFull,
150 CSActionPending
153 KHTMLViewPrivate(KHTMLView* v)
154 : underMouse( 0 ), underMouseNonShared( 0 ), oldUnderMouse( 0 )
156 postponed_autorepeat = NULL;
157 scrollingFromWheelTimerId = 0;
158 smoothScrollMode = KHTMLView::SSMWhenEfficient;
159 reset();
160 vpolicy = Qt::ScrollBarAsNeeded;
161 hpolicy = Qt::ScrollBarAsNeeded;
162 formCompletions=0;
163 prevScrollbarVisible = true;
165 possibleTripleClick = false;
166 emitCompletedAfterRepaint = CSNone;
167 cursorIconWidget = 0;
168 cursorIconType = KHTMLView::LINK_NORMAL;
169 m_mouseScrollTimer = 0;
170 m_mouseScrollIndicator = 0;
171 contentsX = 0;
172 contentsY = 0;
173 view = v;
175 ~KHTMLViewPrivate()
177 delete formCompletions;
178 delete postponed_autorepeat;
179 if (underMouse)
180 underMouse->deref();
181 if (underMouseNonShared)
182 underMouseNonShared->deref();
183 if (oldUnderMouse)
184 oldUnderMouse->deref();
186 delete cursorIconWidget;
187 delete m_mouseScrollTimer;
188 delete m_mouseScrollIndicator;
190 void reset()
192 if (underMouse)
193 underMouse->deref();
194 underMouse = 0;
195 if (underMouseNonShared)
196 underMouseNonShared->deref();
197 underMouseNonShared = 0;
198 if (oldUnderMouse)
199 oldUnderMouse->deref();
200 oldUnderMouse = 0;
201 linkPressed = false;
202 staticWidget = SBNone;
203 fixedObjectsCount = 0;
204 staticObjectsCount = 0;
205 tabMovePending = false;
206 lastTabbingDirection = true;
207 pseudoFocusNode = PFNone;
208 zoomLevel = 100;
209 #ifndef KHTML_NO_SCROLLBARS
210 //We don't turn off the toolbars here
211 //since if the user turns them
212 //off, then chances are they want them turned
213 //off always - even after a reset.
214 #else
215 vpolicy = ScrollBarAlwaysOff;
216 hpolicy = ScrollBarAlwaysOff;
217 #endif
218 scrollBarMoved = false;
219 contentsMoving = false;
220 ignoreWheelEvents = false;
221 scrollingFromWheel = QPoint(-1,-1);
222 borderX = 30;
223 borderY = 30;
224 dx = dy = ddx = ddy = rdx = rdy = dddx = dddy = 0;
225 paged = false;
226 clickX = -1;
227 clickY = -1;
228 clickCount = 0;
229 isDoubleClick = false;
230 scrollingSelf = false;
231 delete postponed_autorepeat;
232 postponed_autorepeat = NULL;
233 layoutTimerId = 0;
234 repaintTimerId = 0;
235 scrollTimerId = 0;
236 scrollSuspended = false;
237 scrollSuspendPreActivate = false;
238 smoothScrolling = false;
239 smoothScrollModeIsDefault = true;
240 shouldSmoothScroll = false;
241 hasFrameset = false;
242 complete = false;
243 firstLayoutPending = true;
244 firstRepaintPending = true;
245 needsFullRepaint = true;
246 dirtyLayout = false;
247 layoutSchedulingEnabled = true;
248 painting = false;
249 layoutCounter = 0;
250 layoutAttemptCounter = 0;
251 scheduledLayoutCounter = 0;
252 updateRegion = QRegion();
253 m_dialogsAllowed = true;
254 #ifndef KHTML_NO_TYPE_AHEAD_FIND
255 typeAheadActivated = false;
256 #endif // KHTML_NO_TYPE_AHEAD_FIND
257 accessKeysActivated = false;
258 accessKeysPreActivate = false;
260 // the view might have been built before the part it will be assigned to,
261 // so exceptionally, we need to directly ref/deref KHTMLGlobal to
262 // account for this transitory case.
263 KHTMLGlobal::ref();
264 accessKeysEnabled = KHTMLGlobal::defaultHTMLSettings()->accessKeysEnabled();
265 KHTMLGlobal::deref();
267 emitCompletedAfterRepaint = CSNone;
268 m_mouseEventsTarget = 0;
269 m_clipHolder = 0;
271 void newScrollTimer(QWidget *view, int tid)
273 //kDebug(6000) << "newScrollTimer timer " << tid;
274 view->killTimer(scrollTimerId);
275 scrollTimerId = tid;
276 scrollSuspended = false;
278 enum ScrollDirection { ScrollLeft, ScrollRight, ScrollUp, ScrollDown };
280 void adjustScroller(QWidget *view, ScrollDirection direction, ScrollDirection oppositedir)
282 static const struct { int msec, pixels; } timings [] = {
283 {320,1}, {224,1}, {160,1}, {112,1}, {80,1}, {56,1}, {40,1},
284 {28,1}, {20,1}, {20,2}, {20,3}, {20,4}, {20,6}, {20,8}, {0,0}
286 if (!scrollTimerId ||
287 (static_cast<int>(scrollDirection) != direction &&
288 (static_cast<int>(scrollDirection) != oppositedir || scrollSuspended))) {
289 scrollTiming = 6;
290 scrollBy = timings[scrollTiming].pixels;
291 scrollDirection = direction;
292 newScrollTimer(view, view->startTimer(timings[scrollTiming].msec));
293 } else if (scrollDirection == direction &&
294 timings[scrollTiming+1].msec && !scrollSuspended) {
295 scrollBy = timings[++scrollTiming].pixels;
296 newScrollTimer(view, view->startTimer(timings[scrollTiming].msec));
297 } else if (scrollDirection == oppositedir) {
298 if (scrollTiming) {
299 scrollBy = timings[--scrollTiming].pixels;
300 newScrollTimer(view, view->startTimer(timings[scrollTiming].msec));
303 scrollSuspended = false;
306 bool haveZoom() const { return zoomLevel != 100; }
308 void startScrolling()
310 smoothScrolling = true;
311 smoothScrollTimer.start(sSmoothScrollTick);
312 shouldSmoothScroll = false;
315 void stopScrolling()
317 smoothScrollTimer.stop();
318 dx = dy = 0;
319 ddx = ddy = 0;
320 rdx = rdy = 0;
321 dddx = dddy = 0;
322 updateContentsXY();
323 smoothScrolling = false;
324 shouldSmoothScroll = false;
327 void updateContentsXY()
329 contentsX = QApplication::isRightToLeft() ?
330 view->horizontalScrollBar()->maximum()-view->horizontalScrollBar()->value() : view->horizontalScrollBar()->value();
331 contentsY = view->verticalScrollBar()->value();
334 void scrollExternalWidgets(int dx, int dy)
336 if (visibleWidgets.isEmpty())
337 return;
339 QHashIterator<void*, QWidget*> it(visibleWidgets);
340 while (it.hasNext()) {
341 it.next();
342 it.value()->move( it.value()->pos() + QPoint(dx, dy) );
346 NodeImpl *underMouse;
347 NodeImpl *underMouseNonShared;
348 NodeImpl *oldUnderMouse;
350 // Do not adjust bitfield enums sizes.
351 // They are oversized because they are signed on some platforms.
352 bool tabMovePending:1;
353 bool lastTabbingDirection:1;
354 PseudoFocusNodes pseudoFocusNode:3;
355 bool scrollBarMoved:1;
356 bool contentsMoving:1;
358 Qt::ScrollBarPolicy vpolicy;
359 Qt::ScrollBarPolicy hpolicy;
360 bool prevScrollbarVisible:1;
361 bool linkPressed:1;
362 bool ignoreWheelEvents:1;
363 StaticBackgroundState staticWidget: 3;
364 int staticObjectsCount;
365 int fixedObjectsCount;
367 int zoomLevel;
368 int borderX, borderY;
369 int dx, dy, ddx, ddy, rdx, rdy, dddx, dddy;
370 KConfig *formCompletions;
372 int clickX, clickY, clickCount;
373 bool isDoubleClick;
375 bool paged;
377 bool scrollingSelf;
378 int contentsX, contentsY;
379 int layoutTimerId;
380 QKeyEvent* postponed_autorepeat;
382 int repaintTimerId;
383 int scrollTimerId;
384 int scrollTiming;
385 int scrollBy;
386 ScrollDirection scrollDirection :3;
387 bool scrollSuspended :1;
388 bool scrollSuspendPreActivate :1;
389 KHTMLView::SmoothScrollingMode smoothScrollMode :3;
390 bool smoothScrolling :1;
391 bool smoothScrollModeIsDefault :1;
392 bool shouldSmoothScroll :1;
393 bool hasFrameset :1;
394 bool complete :1;
395 bool firstLayoutPending :1;
396 bool firstRepaintPending :1;
397 bool layoutSchedulingEnabled :1;
398 bool needsFullRepaint :1;
399 bool painting :1;
400 bool possibleTripleClick :1;
401 bool dirtyLayout :1;
402 bool m_dialogsAllowed :1;
403 int layoutCounter;
404 int layoutAttemptCounter;
405 int scheduledLayoutCounter;
406 QRegion updateRegion;
407 QTimer smoothScrollTimer;
408 QTime smoothScrollStopwatch;
409 QHash<void*, QWidget*> visibleWidgets;
410 #ifndef KHTML_NO_TYPE_AHEAD_FIND
411 QString findString;
412 QTimer timer;
413 bool findLinksOnly;
414 bool typeAheadActivated;
415 #endif // KHTML_NO_TYPE_AHEAD_FIND
416 bool accessKeysEnabled;
417 bool accessKeysActivated;
418 bool accessKeysPreActivate;
419 CompletedState emitCompletedAfterRepaint;
421 QLabel* cursorIconWidget;
422 KHTMLView::LinkCursor cursorIconType;
424 // scrolling activated by MMB
425 short m_mouseScroll_byX;
426 short m_mouseScroll_byY;
427 QPoint scrollingFromWheel;
428 int scrollingFromWheelTimerId;
429 QTimer *m_mouseScrollTimer;
430 QWidget *m_mouseScrollIndicator;
431 QPointer<QWidget> m_mouseEventsTarget;
432 QStack<QRegion>* m_clipHolder;
433 KHTMLView* view;
436 #ifndef QT_NO_TOOLTIP
438 /** calculates the client-side image map rectangle for the given image element
439 * @param img image element
440 * @param scrollOfs scroll offset of viewport in content coordinates
441 * @param p position to be probed in viewport coordinates
442 * @param r returns the bounding rectangle in content coordinates
443 * @param s returns the title string
444 * @return true if an appropriate area was found -- only in this case r and
445 * s are valid, false otherwise
447 static bool findImageMapRect(HTMLImageElementImpl *img, const QPoint &scrollOfs,
448 const QPoint &p, QRect &r, QString &s)
450 HTMLMapElementImpl* map;
451 if (img && img->document()->isHTMLDocument() &&
452 (map = static_cast<HTMLDocumentImpl*>(img->document())->getMap(img->imageMap()))) {
453 RenderObject::NodeInfo info(true, false);
454 RenderObject *rend = img->renderer();
455 int ax, ay;
456 if (!rend || !rend->absolutePosition(ax, ay))
457 return false;
458 // we're a client side image map
459 bool inside = map->mapMouseEvent(p.x() - ax + scrollOfs.x(),
460 p.y() - ay + scrollOfs.y(), rend->contentWidth(),
461 rend->contentHeight(), info);
462 if (inside && info.URLElement()) {
463 HTMLAreaElementImpl *area = static_cast<HTMLAreaElementImpl *>(info.URLElement());
464 Q_ASSERT(area->id() == ID_AREA);
465 s = area->getAttribute(ATTR_TITLE).string();
466 QRegion reg = area->cachedRegion();
467 if (!s.isEmpty() && !reg.isEmpty()) {
468 r = reg.boundingRect();
469 r.translate(ax, ay);
470 return true;
474 return false;
477 bool KHTMLView::event( QEvent* e )
479 switch ( e->type() ) {
480 case QEvent::ToolTip: {
481 QHelpEvent *he = static_cast<QHelpEvent*>(e);
482 QPoint p = he->pos();
484 DOM::NodeImpl *node = d->underMouseNonShared;
485 QRect region;
486 while ( node ) {
487 if ( node->isElementNode() ) {
488 DOM::ElementImpl *e = static_cast<DOM::ElementImpl*>( node );
489 QRect r;
490 QString s;
491 bool found = false;
492 // for images, check if it is part of a client-side image map,
493 // and query the <area>s' title attributes, too
494 if (e->id() == ID_IMG && !e->getAttribute( ATTR_USEMAP ).isEmpty()) {
495 found = findImageMapRect(static_cast<HTMLImageElementImpl *>(e),
496 viewportToContents(QPoint(0, 0)), p, r, s);
498 if (!found) {
499 s = e->getAttribute( ATTR_TITLE ).string();
500 r = node->getRect();
502 region |= QRect( contentsToViewport( r.topLeft() ), r.size() );
503 if ( !s.isEmpty() ) {
504 QToolTip::showText( viewport()->mapToGlobal(region.bottomLeft()),
505 Qt::convertFromPlainText( s, Qt::WhiteSpaceNormal ) );
506 break;
509 node = node->parentNode();
511 return true;
514 case QEvent::DragEnter:
515 case QEvent::DragMove:
516 case QEvent::DragLeave:
517 case QEvent::Drop:
518 // In Qt4, one needs to both call accept() on the DND event and return
519 // true on ::event for the candidate widget for the drop to be possible.
520 // Apps hosting us, such as konq, can do the former but not the later.
521 // We will do the second bit, as it's a no-op unless someone else explicitly
522 // accepts the event. We need to skip the scrollarea to do that,
523 // since it will just skip the events, both killing the drop, and
524 // not permitting us to forward it up the part hiearchy in our dragEnterEvent,
525 // etc. handlers
526 return QWidget::event(e);
527 case QEvent::StyleChange:
528 case QEvent::LayoutRequest: {
529 updateScrollBars();
530 return QAbstractScrollArea::event(e);
532 case QEvent::PaletteChange:
533 slotPaletteChanged();
534 return QScrollArea::event(e);
535 default:
536 return QScrollArea::event(e);
539 #endif
541 KHTMLView::KHTMLView( KHTMLPart *part, QWidget *parent )
542 : QScrollArea( parent ), d( new KHTMLViewPrivate( this ) )
544 m_medium = "screen";
546 m_part = part;
548 QScrollArea::setVerticalScrollBarPolicy(d->vpolicy);
549 QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy);
551 #ifndef KHTML_NO_TYPE_AHEAD_FIND
552 connect(&d->timer, SIGNAL(timeout()), this, SLOT(findTimeout()));
553 #endif // KHTML_NO_TYPE_AHEAD_FIND
555 init();
556 widget()->setMouseTracking(true);
559 KHTMLView::~KHTMLView()
561 closeChildDialogs();
562 if (m_part)
564 DOM::DocumentImpl *doc = m_part->xmlDocImpl();
565 if (doc)
566 doc->detach();
568 delete d;
571 void KHTMLView::setPart(KHTMLPart *part)
573 assert(part && !m_part);
574 m_part = part;
577 void KHTMLView::init()
579 // Do not access the part here. It might not be fully constructed.
581 setFrameStyle(QFrame::NoFrame);
582 setFocusPolicy(Qt::StrongFocus);
583 viewport()->setFocusProxy(this);
585 _marginWidth = -1; // undefined
586 _marginHeight = -1;
587 _width = 0;
588 _height = 0;
590 installEventFilter(this);
592 setAcceptDrops(true);
593 if (!widget())
594 setWidget( new QWidget(this) );
595 widget()->setAttribute( Qt::WA_NoSystemBackground );
597 verticalScrollBar()->setCursor( Qt::ArrowCursor );
598 horizontalScrollBar()->setCursor( Qt::ArrowCursor );
600 connect(&d->smoothScrollTimer, SIGNAL(timeout()), this, SLOT(scrollTick()));
603 void KHTMLView::resizeContentsToViewport()
605 QSize s = viewport()->size();
606 resizeContents(s.width(), s.height());
610 // called by KHTMLPart::clear()
611 void KHTMLView::clear()
613 #ifndef KHTML_NO_TYPE_AHEAD_FIND
614 if( d->typeAheadActivated )
615 findTimeout();
616 #endif
617 if (d->accessKeysEnabled && d->accessKeysActivated)
618 accessKeysTimeout();
619 viewport()->unsetCursor();
620 if ( d->cursorIconWidget )
621 d->cursorIconWidget->hide();
622 if (d->smoothScrolling)
623 d->stopScrolling();
624 d->reset();
625 QAbstractEventDispatcher::instance()->unregisterTimers(this);
626 emit cleared();
628 QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy);
629 QScrollArea::setVerticalScrollBarPolicy(d->vpolicy);
630 verticalScrollBar()->setEnabled( false );
631 horizontalScrollBar()->setEnabled( false );
635 void KHTMLView::hideEvent(QHideEvent* e)
637 QScrollArea::hideEvent(e);
638 if ( m_part && m_part->xmlDocImpl() )
639 m_part->xmlDocImpl()->docLoader()->pauseAnimations();
642 void KHTMLView::showEvent(QShowEvent* e)
644 QScrollArea::showEvent(e);
645 if ( m_part && m_part->xmlDocImpl() )
646 m_part->xmlDocImpl()->docLoader()->resumeAnimations();
649 void KHTMLView::setMouseEventsTarget( QWidget* w )
651 d->m_mouseEventsTarget = w;
654 QWidget* KHTMLView::mouseEventsTarget() const
656 return d->m_mouseEventsTarget;
659 void KHTMLView::setClipHolder( QStack<QRegion>* ch )
661 d->m_clipHolder = ch;
664 QStack<QRegion>* KHTMLView::clipHolder() const
666 return d->m_clipHolder;
669 int KHTMLView::contentsWidth() const
671 return widget() ? widget()->width() : 0;
674 int KHTMLView::contentsHeight() const
676 return widget() ? widget()->height() : 0;
679 void KHTMLView::resizeContents(int w, int h)
681 if (!widget())
682 return;
683 widget()->resize(w, h);
684 if (!widget()->isVisible())
685 updateScrollBars();
688 int KHTMLView::contentsX() const
690 return d->contentsX;
693 int KHTMLView::contentsY() const
695 return d->contentsY;
698 int KHTMLView::visibleWidth() const
700 if (m_kwp->isRedirected()) {
701 // our RenderWidget knows better
702 if (RenderWidget* rw = m_kwp->renderWidget()) {
703 int ret = rw->width()-rw->paddingLeft()-rw->paddingRight()-rw->borderLeft()-rw->borderRight();
704 if (verticalScrollBar()->isVisible()) {
705 ret -= verticalScrollBar()->sizeHint().width();
706 ret = qMax(0, ret);
708 return ret;
711 return viewport()->width();
714 int KHTMLView::visibleHeight() const
716 if (m_kwp->isRedirected()) {
717 // our RenderWidget knows better
718 if (RenderWidget* rw = m_kwp->renderWidget()) {
719 int ret = rw->height()-rw->paddingBottom()-rw->paddingTop()-rw->borderTop()-rw->borderBottom();
720 if (horizontalScrollBar()->isVisible()) {
721 ret -= horizontalScrollBar()->sizeHint().height();
722 ret = qMax(0, ret);
724 return ret;
727 return viewport()->height();
730 void KHTMLView::setContentsPos( int x, int y)
732 horizontalScrollBar()->setValue( QApplication::isRightToLeft() ?
733 horizontalScrollBar()->maximum()-x : x );
734 verticalScrollBar()->setValue( y );
737 void KHTMLView::scrollBy(int x, int y)
739 horizontalScrollBar()->setValue( horizontalScrollBar()->value()+x );
740 verticalScrollBar()->setValue( verticalScrollBar()->value()+y );
743 QPoint KHTMLView::contentsToViewport(const QPoint& p) const
745 return QPoint(p.x()-contentsX(), p.y()-contentsY());
748 void KHTMLView::contentsToViewport(int x, int y, int& cx, int& cy) const
750 QPoint p(x,y);
751 p = contentsToViewport(p);
752 cx = p.x();
753 cy = p.y();
756 QPoint KHTMLView::viewportToContents(const QPoint& p) const
758 return QPoint(p.x()+contentsX(), p.y()+contentsY());
761 void KHTMLView::viewportToContents(int x, int y, int& cx, int& cy) const
763 QPoint p(x,y);
764 p = viewportToContents(p);
765 cx = p.x();
766 cy = p.y();
769 void KHTMLView::updateContents(int x, int y, int w, int h)
771 applyTransforms(x, y, w, h);
772 if (m_kwp->isRedirected()) {
773 QPoint off = m_kwp->absolutePos();
774 KHTMLView* pview = m_part->parentPart()->view();
775 pview->updateContents(x+off.x(), y+off.y(), w, h);
776 } else
777 widget()->update(x, y, w, h);
780 void KHTMLView::updateContents( const QRect& r )
782 updateContents( r.x(), r.y(), r.width(), r.height() );
785 void KHTMLView::repaintContents(int x, int y, int w, int h)
787 applyTransforms(x, y, w, h);
788 if (m_kwp->isRedirected()) {
789 QPoint off = m_kwp->absolutePos();
790 KHTMLView* pview = m_part->parentPart()->view();
791 pview->repaintContents(x+off.x(), y+off.y(), w, h);
792 } else
793 widget()->repaint(x, y, w, h);
796 void KHTMLView::repaintContents( const QRect& r )
798 repaintContents( r.x(), r.y(), r.width(), r.height() );
801 void KHTMLView::applyTransforms( int& x, int& y, int& w, int& h) const
803 if (d->haveZoom()) {
804 const int z = d->zoomLevel;
805 x = x*z/100;
806 y = y*z/100;
807 w = w*z/100;
808 h = h*z/100;
810 x -= contentsX();
811 y -= contentsY();
814 void KHTMLView::revertTransforms( int& x, int& y, int& w, int& h) const
816 x += contentsX();
817 y += contentsY();
818 if (d->haveZoom()) {
819 const int z = d->zoomLevel;
820 x = x*100/z;
821 y = y*100/z;
822 w = w*100/z;
823 h = h*100/z;
827 void KHTMLView::revertTransforms( int& x, int& y ) const
829 int dummy = 0;
830 revertTransforms(x, y, dummy, dummy);
833 void KHTMLView::resizeEvent (QResizeEvent* /*e*/)
835 updateScrollBars();
837 // If we didn't load anything, make white area as big as the view
838 if (!m_part->xmlDocImpl())
839 resizeContentsToViewport();
841 // Viewport-dependent media queries may cause us to need completely different style information.
842 if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->styleSelector()->affectedByViewportChange()) {
843 m_part->xmlDocImpl()->updateStyleSelector();
846 if (d->layoutSchedulingEnabled)
847 layout();
849 QApplication::sendPostedEvents(viewport(), QEvent::Paint);
851 if ( m_part && m_part->xmlDocImpl() ) {
852 if (m_part->parentPart()) {
853 // sub-frame : queue the resize event until our toplevel is done layouting
854 khtml::ChildFrame *cf = m_part->parentPart()->frame( m_part );
855 cf->m_partContainerElement->postResizeEvent();
856 } else {
857 // toplevel : dispatch sub-frames'resize events before our own
858 HTMLPartContainerElementImpl::sendPostedResizeEvents();
859 m_part->xmlDocImpl()->dispatchWindowEvent( EventImpl::RESIZE_EVENT, false, false );
864 void KHTMLView::paintEvent( QPaintEvent *e )
866 QPainter p(widget());
868 QRect r = e->rect();
869 QRect v(contentsX(), contentsY(), visibleWidth(), visibleHeight());
870 QPoint off(contentsX(),contentsY());
871 p.translate(-off);
872 r.translate(off);
874 r = r.intersect(v);
876 if (!r.isValid() || r.isEmpty()) return;
878 if (d->haveZoom()) {
879 p.scale( d->zoomLevel/100., d->zoomLevel/100.);
881 r.setX(r.x()*100/d->zoomLevel);
882 r.setY(r.y()*100/d->zoomLevel);
883 r.setWidth(r.width()*100/d->zoomLevel);
884 r.setHeight(r.height()*100/d->zoomLevel);
885 r.adjust(-1,-1,1,1);
887 p.setClipRect(r);
889 int ex = r.x();
890 int ey = r.y();
891 int ew = r.width();
892 int eh = r.height();
894 if(!m_part || !m_part->xmlDocImpl() || !m_part->xmlDocImpl()->renderer()) {
895 p.fillRect(ex, ey, ew, eh, palette().brush(QPalette::Active, QPalette::Base));
896 return;
897 } else if ( d->complete && static_cast<RenderCanvas*>(m_part->xmlDocImpl()->renderer())->needsLayout() ) {
898 // an external update request happens while we have a layout scheduled
899 unscheduleRelayout();
900 layout();
901 } else if (m_part->xmlDocImpl()->tokenizer()) {
902 m_part->xmlDocImpl()->tokenizer()->setNormalYeldDelay();
905 if (d->painting) {
906 kDebug( 6000 ) << "WARNING: paintEvent reentered! ";
907 kDebug( 6000 ) << kBacktrace();
908 return;
910 d->painting = true;
912 m_part->xmlDocImpl()->renderer()->layer()->paint(&p, r);
914 if (d->hasFrameset) {
915 NodeImpl *body = static_cast<HTMLDocumentImpl*>(m_part->xmlDocImpl())->body();
916 if(body && body->renderer() && body->id() == ID_FRAMESET)
917 static_cast<RenderFrameSet*>(body->renderer())->paintFrameSetRules(&p, r);
918 else
919 d->hasFrameset = false;
922 khtml::DrawContentsEvent event( &p, ex, ey, ew, eh );
923 QApplication::sendEvent( m_part, &event );
925 if (d->contentsMoving && !d->smoothScrolling && widget()->underMouse()) {
926 QMouseEvent *tempEvent = new QMouseEvent( QEvent::MouseMove, widget()->mapFromGlobal( QCursor::pos() ),
927 Qt::NoButton, Qt::NoButton, Qt::NoModifier );
928 QApplication::postEvent(widget(), tempEvent);
931 d->painting = false;
932 d->firstRepaintPending = false;
935 void KHTMLView::setMarginWidth(int w)
937 // make it update the rendering area when set
938 _marginWidth = w;
941 void KHTMLView::setMarginHeight(int h)
943 // make it update the rendering area when set
944 _marginHeight = h;
947 void KHTMLView::layout()
949 if( m_part && m_part->xmlDocImpl() ) {
950 DOM::DocumentImpl *document = m_part->xmlDocImpl();
952 khtml::RenderCanvas* canvas = static_cast<khtml::RenderCanvas *>(document->renderer());
953 if ( !canvas ) return;
955 d->layoutSchedulingEnabled=false;
956 d->dirtyLayout = true;
958 // the reference object for the overflow property on canvas
959 RenderObject * ref = 0;
960 RenderObject* root = document->documentElement() ? document->documentElement()->renderer() : 0;
962 if (document->isHTMLDocument()) {
963 NodeImpl *body = static_cast<HTMLDocumentImpl*>(document)->body();
964 if(body && body->renderer() && body->id() == ID_FRAMESET) {
965 QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
966 QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
967 body->renderer()->setNeedsLayout(true);
968 d->hasFrameset = true;
970 else if (root) // only apply body's overflow to canvas if root has a visible overflow
971 ref = (!body || root->style()->hidesOverflow()) ? root : body->renderer();
972 } else {
973 ref = root;
975 if (ref) {
976 if( ref->style()->overflowX() == OHIDDEN ) {
977 if (d->hpolicy == Qt::ScrollBarAsNeeded) QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
978 } else if (ref->style()->overflowX() == OSCROLL ) {
979 if (d->hpolicy == Qt::ScrollBarAsNeeded) QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
980 } else if (horizontalScrollBarPolicy() != d->hpolicy) {
981 QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy);
983 if ( ref->style()->overflowY() == OHIDDEN ) {
984 if (d->vpolicy == Qt::ScrollBarAsNeeded) QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
985 } else if (ref->style()->overflowY() == OSCROLL ) {
986 if (d->vpolicy == Qt::ScrollBarAsNeeded) QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
987 } else if (verticalScrollBarPolicy() != d->vpolicy) {
988 QScrollArea::setVerticalScrollBarPolicy(d->vpolicy);
991 d->needsFullRepaint = d->firstLayoutPending;
992 if (_height != visibleHeight() || _width != visibleWidth()) {;
993 d->needsFullRepaint = true;
994 _height = visibleHeight();
995 _width = visibleWidth();
998 canvas->layout();
1000 emit finishedLayout();
1001 if (d->firstLayoutPending) {
1002 // make sure firstLayoutPending is set to false now in case this layout
1003 // wasn't scheduled
1004 d->firstLayoutPending = false;
1005 verticalScrollBar()->setEnabled( true );
1006 horizontalScrollBar()->setEnabled( true );
1008 d->layoutCounter++;
1010 if (d->accessKeysEnabled && d->accessKeysActivated) {
1011 emit hideAccessKeys();
1012 displayAccessKeys();
1015 else
1016 _width = visibleWidth();
1018 if (d->layoutTimerId)
1019 killTimer(d->layoutTimerId);
1020 d->layoutTimerId = 0;
1021 d->layoutSchedulingEnabled=true;
1024 void KHTMLView::closeChildDialogs()
1026 QList<QDialog *> dlgs = findChildren<QDialog *>();
1027 foreach (QDialog *dlg, dlgs)
1029 KDialog* dlgbase = dynamic_cast<KDialog*>( dlg );
1030 if ( dlgbase ) {
1031 if ( dlgbase->testAttribute( Qt::WA_ShowModal ) ) {
1032 kDebug(6000) << "closeChildDialogs: closing dialog " << dlgbase;
1033 // close() ends up calling QButton::animateClick, which isn't immediate
1034 // we need something the exits the event loop immediately (#49068)
1035 dlgbase->reject();
1038 else
1040 kWarning() << "closeChildDialogs: not a KDialog! Don't use QDialogs in KDE! " << static_cast<QWidget*>(dlg);
1041 static_cast<QWidget*>(dlg)->hide();
1044 d->m_dialogsAllowed = false;
1047 bool KHTMLView::dialogsAllowed() {
1048 bool allowed = d->m_dialogsAllowed;
1049 KHTMLPart* p = m_part->parentPart();
1050 if (p && p->view())
1051 allowed &= p->view()->dialogsAllowed();
1052 return allowed;
1055 void KHTMLView::closeEvent( QCloseEvent* ev )
1057 closeChildDialogs();
1058 QScrollArea::closeEvent( ev );
1061 void KHTMLView::setZoomLevel(int percent)
1063 percent = percent < 20 ? 20 : (percent > 800 ? 800 : percent);
1064 int oldpercent = d->zoomLevel;
1065 d->zoomLevel = percent;
1066 if (percent != oldpercent) {
1067 if (d->layoutSchedulingEnabled)
1068 layout();
1069 widget()->update();
1073 int KHTMLView::zoomLevel() const
1075 return d->zoomLevel;
1078 void KHTMLView::setSmoothScrollingMode( SmoothScrollingMode m )
1080 d->smoothScrollMode = m;
1081 d->smoothScrollModeIsDefault = false;
1082 if (d->smoothScrolling && !m)
1083 d->stopScrolling();
1086 void KHTMLView::setSmoothScrollingModeDefault( SmoothScrollingMode m )
1088 // check for manual override
1089 if (!d->smoothScrollModeIsDefault)
1090 return;
1091 d->smoothScrollMode = m;
1092 if (d->smoothScrolling && !m)
1093 d->stopScrolling();
1096 KHTMLView::SmoothScrollingMode KHTMLView::smoothScrollingMode( ) const
1098 return d->smoothScrollMode;
1102 // Event Handling
1104 /////////////////
1106 void KHTMLView::mousePressEvent( QMouseEvent *_mouse )
1108 if (!m_part->xmlDocImpl()) return;
1109 if (d->possibleTripleClick && ( _mouse->button() & Qt::MouseButtonMask ) == Qt::LeftButton)
1111 mouseDoubleClickEvent( _mouse ); // it handles triple clicks too
1112 return;
1115 int xm = _mouse->x();
1116 int ym = _mouse->y();
1117 revertTransforms(xm, ym);
1119 // kDebug( 6000 ) << "mousePressEvent: viewport=("<<_mouse->x()-contentsX()<<"/"<<_mouse->y()-contentsY()<<"), contents=(" << xm << "/" << ym << ")\n";
1121 d->isDoubleClick = false;
1123 DOM::NodeImpl::MouseEvent mev( _mouse->buttons(), DOM::NodeImpl::MousePress );
1124 m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev );
1126 //kDebug(6000) << "innerNode="<<mev.innerNode.nodeName().string();
1128 if ( (_mouse->button() == Qt::MidButton) &&
1129 !m_part->d->m_bOpenMiddleClick && !d->m_mouseScrollTimer &&
1130 mev.url.isNull() && (mev.innerNode.elementId() != ID_INPUT) ) {
1131 QPoint point = mapFromGlobal( _mouse->globalPos() );
1133 d->m_mouseScroll_byX = 0;
1134 d->m_mouseScroll_byY = 0;
1136 d->m_mouseScrollTimer = new QTimer( this );
1137 connect( d->m_mouseScrollTimer, SIGNAL(timeout()), this, SLOT(slotMouseScrollTimer()) );
1139 if ( !d->m_mouseScrollIndicator ) {
1140 QPixmap pixmap( 48, 48 ), icon;
1141 pixmap.fill( QColor( qRgba( 127, 127, 127, 127 ) ) );
1143 QPainter p( &pixmap );
1144 QStyleOption option;
1146 option.rect.setRect( 16, 0, 16, 16 );
1147 QApplication::style()->drawPrimitive( QStyle::PE_IndicatorArrowUp, &option, &p );
1148 option.rect.setRect( 0, 16, 16, 16 );
1149 QApplication::style()->drawPrimitive( QStyle::PE_IndicatorArrowLeft, &option, &p );
1150 option.rect.setRect( 16, 32, 16, 16 );
1151 QApplication::style()->drawPrimitive( QStyle::PE_IndicatorArrowDown, &option, &p );
1152 option.rect.setRect( 32, 16, 16, 16 );
1153 QApplication::style()->drawPrimitive( QStyle::PE_IndicatorArrowRight, &option, &p );
1154 p.drawEllipse( 23, 23, 2, 2 );
1156 d->m_mouseScrollIndicator = new QWidget( this );
1157 d->m_mouseScrollIndicator->setFixedSize( 48, 48 );
1158 QPalette palette;
1159 palette.setBrush( d->m_mouseScrollIndicator->backgroundRole(), QBrush( pixmap ) );
1160 d->m_mouseScrollIndicator->setPalette( palette );
1162 d->m_mouseScrollIndicator->move( point.x()-24, point.y()-24 );
1164 bool hasHorBar = visibleWidth() < contentsWidth();
1165 bool hasVerBar = visibleHeight() < contentsHeight();
1167 KConfigGroup cg( KGlobal::config(), "HTML Settings" );
1168 if ( cg.readEntry( "ShowMouseScrollIndicator", true ) ) {
1169 d->m_mouseScrollIndicator->show();
1170 d->m_mouseScrollIndicator->unsetCursor();
1172 QBitmap mask = d->m_mouseScrollIndicator->palette().brush(d->m_mouseScrollIndicator->backgroundRole()).texture().createHeuristicMask( true );
1174 if ( hasHorBar && !hasVerBar ) {
1175 QBitmap bm( 16, 16 );
1176 bm.clear();
1177 QPainter painter( &mask );
1178 painter.drawPixmap( QRectF( 16, 0, bm.width(), bm.height() ), bm, bm.rect() );
1179 painter.drawPixmap( QRectF( 16, 32, bm.width(), bm.height() ), bm, bm.rect() );
1180 d->m_mouseScrollIndicator->setCursor( Qt::SizeHorCursor );
1182 else if ( !hasHorBar && hasVerBar ) {
1183 QBitmap bm( 16, 16 );
1184 bm.clear();
1185 QPainter painter( &mask );
1186 painter.drawPixmap( QRectF( 0, 16, bm.width(), bm.height() ), bm, bm.rect() );
1187 painter.drawPixmap( QRectF( 32, 16, bm.width(), bm.height() ), bm, bm.rect() );
1188 d->m_mouseScrollIndicator->setCursor( Qt::SizeVerCursor );
1190 else
1191 d->m_mouseScrollIndicator->setCursor( Qt::SizeAllCursor );
1193 d->m_mouseScrollIndicator->setMask( mask );
1195 else {
1196 if ( hasHorBar && !hasVerBar )
1197 viewport()->setCursor( Qt::SizeHorCursor );
1198 else if ( !hasHorBar && hasVerBar )
1199 viewport()->setCursor( Qt::SizeVerCursor );
1200 else
1201 viewport()->setCursor( Qt::SizeAllCursor );
1204 return;
1206 else if ( d->m_mouseScrollTimer ) {
1207 delete d->m_mouseScrollTimer;
1208 d->m_mouseScrollTimer = 0;
1210 if ( d->m_mouseScrollIndicator )
1211 d->m_mouseScrollIndicator->hide();
1214 if (d->clickCount > 0 &&
1215 QPoint(d->clickX-xm,d->clickY-ym).manhattanLength() <= QApplication::startDragDistance())
1216 d->clickCount++;
1217 else {
1218 d->clickCount = 1;
1219 d->clickX = xm;
1220 d->clickY = ym;
1223 bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEDOWN_EVENT,mev.innerNode.handle(),mev.innerNonSharedNode.handle(),true,
1224 d->clickCount,_mouse,true,DOM::NodeImpl::MousePress);
1226 if (!swallowEvent) {
1227 emit m_part->nodeActivated(mev.innerNode);
1229 khtml::MousePressEvent event( _mouse, xm, ym, mev.url, mev.target, mev.innerNode );
1230 QApplication::sendEvent( m_part, &event );
1231 // we might be deleted after this
1235 void KHTMLView::mouseDoubleClickEvent( QMouseEvent *_mouse )
1237 if(!m_part->xmlDocImpl()) return;
1239 int xm = _mouse->x();
1240 int ym = _mouse->y();
1241 revertTransforms(xm, ym);
1243 // kDebug( 6000 ) << "mouseDblClickEvent: x=" << xm << ", y=" << ym;
1245 d->isDoubleClick = true;
1247 DOM::NodeImpl::MouseEvent mev( _mouse->buttons(), DOM::NodeImpl::MouseDblClick );
1248 m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev );
1250 // We do the same thing as mousePressEvent() here, since the DOM does not treat
1251 // single and double-click events as separate (only the detail, i.e. number of clicks differs)
1252 if (d->clickCount > 0 &&
1253 QPoint(d->clickX-xm,d->clickY-ym).manhattanLength() <= QApplication::startDragDistance())
1254 d->clickCount++;
1255 else { // shouldn't happen, if Qt has the same criterias for double clicks.
1256 d->clickCount = 1;
1257 d->clickX = xm;
1258 d->clickY = ym;
1260 bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEDOWN_EVENT,mev.innerNode.handle(),mev.innerNonSharedNode.handle(),true,
1261 d->clickCount,_mouse,true,DOM::NodeImpl::MouseDblClick);
1263 if (!swallowEvent) {
1264 khtml::MouseDoubleClickEvent event( _mouse, xm, ym, mev.url, mev.target, mev.innerNode, d->clickCount );
1265 QApplication::sendEvent( m_part, &event );
1268 d->possibleTripleClick=true;
1269 QTimer::singleShot(QApplication::doubleClickInterval(),this,SLOT(tripleClickTimeout()));
1272 void KHTMLView::tripleClickTimeout()
1274 d->possibleTripleClick = false;
1275 d->clickCount = 0;
1278 static bool targetOpensNewWindow(KHTMLPart *part, QString target)
1280 if (!target.isEmpty() && (target.toLower() != "_top") &&
1281 (target.toLower() != "_self") && (target.toLower() != "_parent")) {
1282 if (target.toLower() == "_blank")
1283 return true;
1284 else {
1285 while (part->parentPart())
1286 part = part->parentPart();
1287 if (!part->frameExists(target))
1288 return true;
1291 return false;
1294 void KHTMLView::mouseMoveEvent( QMouseEvent * _mouse )
1296 if ( d->m_mouseScrollTimer ) {
1297 QPoint point = mapFromGlobal( _mouse->globalPos() );
1299 int deltaX = point.x() - d->m_mouseScrollIndicator->x() - 24;
1300 int deltaY = point.y() - d->m_mouseScrollIndicator->y() - 24;
1302 (deltaX > 0) ? d->m_mouseScroll_byX = 1 : d->m_mouseScroll_byX = -1;
1303 (deltaY > 0) ? d->m_mouseScroll_byY = 1 : d->m_mouseScroll_byY = -1;
1305 double adX = qAbs(deltaX)/30.0;
1306 double adY = qAbs(deltaY)/30.0;
1308 d->m_mouseScroll_byX = qMax(qMin(d->m_mouseScroll_byX * int(adX*adX), SHRT_MAX), SHRT_MIN);
1309 d->m_mouseScroll_byY = qMax(qMin(d->m_mouseScroll_byY * int(adY*adY), SHRT_MAX), SHRT_MIN);
1311 if (d->m_mouseScroll_byX == 0 && d->m_mouseScroll_byY == 0) {
1312 d->m_mouseScrollTimer->stop();
1314 else if (!d->m_mouseScrollTimer->isActive()) {
1315 d->m_mouseScrollTimer->start( 20 );
1319 if(!m_part->xmlDocImpl()) return;
1321 int xm = _mouse->x();
1322 int ym = _mouse->y();
1323 revertTransforms(xm, ym);
1325 DOM::NodeImpl::MouseEvent mev( _mouse->buttons(), DOM::NodeImpl::MouseMove );
1326 // Do not modify :hover/:active state while mouse is pressed.
1327 m_part->xmlDocImpl()->prepareMouseEvent( _mouse->buttons() /*readonly ?*/, xm, ym, &mev );
1329 // kDebug(6000) << "mouse move: " << _mouse->pos()
1330 // << " button " << _mouse->button()
1331 // << " state " << _mouse->state() << endl;
1333 DOM::NodeImpl* target = mev.innerNode.handle();
1334 DOM::NodeImpl* fn = m_part->xmlDocImpl()->focusNode();
1336 // a widget may be the real target of this event (e.g. if a scrollbar's slider is being moved)
1337 if (d->m_mouseEventsTarget && fn && fn->renderer() && fn->renderer()->isWidget())
1338 target = fn;
1340 bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEMOVE_EVENT,target,mev.innerNonSharedNode.handle(),false,
1341 0,_mouse,true,DOM::NodeImpl::MouseMove);
1343 if (d->clickCount > 0 &&
1344 QPoint(d->clickX-xm,d->clickY-ym).manhattanLength() > QApplication::startDragDistance()) {
1345 d->clickCount = 0; // moving the mouse outside the threshold invalidates the click
1348 khtml::RenderObject* r = target ? target->renderer() : 0;
1349 bool setCursor = true;
1350 if (r && r->isWidget()) {
1351 RenderWidget* rw = static_cast<RenderWidget*>(r);
1352 KHTMLWidget* kw = qobject_cast<KHTMLView*>(rw->widget())? dynamic_cast<KHTMLWidget*>(rw->widget()) : 0;
1353 if (kw && kw->m_kwp->isRedirected())
1354 setCursor = false;
1356 khtml::RenderStyle* style = (r && r->style()) ? r->style() : 0;
1357 QCursor c;
1358 LinkCursor linkCursor = LINK_NORMAL;
1359 switch ( style ? style->cursor() : CURSOR_AUTO) {
1360 case CURSOR_AUTO:
1361 if ( r && r->isText() && !r->isPointInsideSelection(xm, ym, m_part->caret()) )
1362 c = QCursor(Qt::IBeamCursor);
1363 if ( mev.url.length() && m_part->settings()->changeCursor() ) {
1364 c = m_part->urlCursor();
1365 if (mev.url.string().startsWith("mailto:") && mev.url.string().indexOf('@')>0)
1366 linkCursor = LINK_MAILTO;
1367 else
1368 if ( targetOpensNewWindow( m_part, mev.target.string() ) )
1369 linkCursor = LINK_NEWWINDOW;
1372 if (r && r->isFrameSet() && !static_cast<RenderFrameSet*>(r)->noResize())
1373 c = QCursor(static_cast<RenderFrameSet*>(r)->cursorShape());
1375 break;
1376 case CURSOR_CROSS:
1377 c = QCursor(Qt::CrossCursor);
1378 break;
1379 case CURSOR_POINTER:
1380 c = m_part->urlCursor();
1381 if (mev.url.string().startsWith("mailto:") && mev.url.string().indexOf('@')>0)
1382 linkCursor = LINK_MAILTO;
1383 else
1384 if ( targetOpensNewWindow( m_part, mev.target.string() ) )
1385 linkCursor = LINK_NEWWINDOW;
1386 break;
1387 case CURSOR_PROGRESS:
1388 c = QCursor(Qt::BusyCursor); // working_cursor
1389 break;
1390 case CURSOR_MOVE:
1391 case CURSOR_ALL_SCROLL:
1392 c = QCursor(Qt::SizeAllCursor);
1393 break;
1394 case CURSOR_E_RESIZE:
1395 case CURSOR_W_RESIZE:
1396 case CURSOR_EW_RESIZE:
1397 c = QCursor(Qt::SizeHorCursor);
1398 break;
1399 case CURSOR_N_RESIZE:
1400 case CURSOR_S_RESIZE:
1401 case CURSOR_NS_RESIZE:
1402 c = QCursor(Qt::SizeVerCursor);
1403 break;
1404 case CURSOR_NE_RESIZE:
1405 case CURSOR_SW_RESIZE:
1406 case CURSOR_NESW_RESIZE:
1407 c = QCursor(Qt::SizeBDiagCursor);
1408 break;
1409 case CURSOR_NW_RESIZE:
1410 case CURSOR_SE_RESIZE:
1411 case CURSOR_NWSE_RESIZE:
1412 c = QCursor(Qt::SizeFDiagCursor);
1413 break;
1414 case CURSOR_TEXT:
1415 c = QCursor(Qt::IBeamCursor);
1416 break;
1417 case CURSOR_WAIT:
1418 c = QCursor(Qt::WaitCursor);
1419 break;
1420 case CURSOR_HELP:
1421 c = QCursor(Qt::WhatsThisCursor);
1422 break;
1423 case CURSOR_DEFAULT:
1424 break;
1425 case CURSOR_NONE:
1426 case CURSOR_NOT_ALLOWED:
1427 c = QCursor(Qt::ForbiddenCursor);
1428 break;
1429 case CURSOR_ROW_RESIZE:
1430 c = QCursor(Qt::SplitVCursor);
1431 break;
1432 case CURSOR_COL_RESIZE:
1433 c = QCursor(Qt::SplitHCursor);
1434 break;
1435 case CURSOR_VERTICAL_TEXT:
1436 case CURSOR_CONTEXT_MENU:
1437 case CURSOR_NO_DROP:
1438 case CURSOR_CELL:
1439 case CURSOR_COPY:
1440 case CURSOR_ALIAS:
1441 c = QCursor(Qt::ArrowCursor);
1442 break;
1445 if (!setCursor && style && style->cursor() != CURSOR_AUTO)
1446 setCursor = true;
1448 QWidget* vp = viewport();
1449 for (KHTMLPart* p = m_part; p; p = p->parentPart())
1450 if (!p->parentPart())
1451 vp = p->view()->viewport();
1452 if ( setCursor && vp->cursor().handle() != c.handle() ) {
1453 if( c.shape() == Qt::ArrowCursor) {
1454 for (KHTMLPart* p = m_part; p; p = p->parentPart())
1455 p->view()->viewport()->unsetCursor();
1457 else {
1458 vp->setCursor( c );
1462 if ( linkCursor!=LINK_NORMAL && isVisible() && hasFocus() ) {
1463 #ifdef Q_WS_X11
1465 if( !d->cursorIconWidget ) {
1466 #ifdef Q_WS_X11
1467 d->cursorIconWidget = new QLabel( 0, Qt::X11BypassWindowManagerHint );
1468 XSetWindowAttributes attr;
1469 attr.save_under = True;
1470 XChangeWindowAttributes( QX11Info::display(), d->cursorIconWidget->winId(), CWSaveUnder, &attr );
1471 #else
1472 d->cursorIconWidget = new QLabel( NULL, NULL );
1473 //TODO
1474 #endif
1477 // Update the pixmap if need be.
1478 if (linkCursor != d->cursorIconType) {
1479 d->cursorIconType = linkCursor;
1480 QString cursorIcon;
1481 switch (linkCursor)
1483 case LINK_MAILTO: cursorIcon = "mail-message-new"; break;
1484 case LINK_NEWWINDOW: cursorIcon = "window-new"; break;
1485 default: cursorIcon = "dialog-error"; break;
1488 QPixmap icon_pixmap = KHTMLGlobal::iconLoader()->loadIcon( cursorIcon, KIconLoader::Small, 0, KIconLoader::DefaultState, QStringList(), 0, true );
1490 d->cursorIconWidget->resize( icon_pixmap.width(), icon_pixmap.height());
1491 d->cursorIconWidget->setMask( icon_pixmap.createMaskFromColor(Qt::transparent));
1492 d->cursorIconWidget->setPixmap( icon_pixmap);
1493 d->cursorIconWidget->update();
1496 QPoint c_pos = QCursor::pos();
1497 d->cursorIconWidget->move( c_pos.x() + 15, c_pos.y() + 15 );
1498 #ifdef Q_WS_X11
1499 XRaiseWindow( QX11Info::display(), d->cursorIconWidget->winId());
1500 QApplication::flush();
1501 #elif defined(Q_WS_WIN)
1502 SetWindowPos( d->cursorIconWidget->winId(), HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE );
1503 #else
1504 //TODO?
1505 #endif
1506 d->cursorIconWidget->show();
1507 #endif
1509 else if ( d->cursorIconWidget )
1510 d->cursorIconWidget->hide();
1512 if (r && r->isWidget()) {
1513 _mouse->ignore();
1516 if (!swallowEvent) {
1517 khtml::MouseMoveEvent event( _mouse, xm, ym, mev.url, mev.target, mev.innerNode );
1518 QApplication::sendEvent( m_part, &event );
1522 void KHTMLView::mouseReleaseEvent( QMouseEvent * _mouse )
1524 bool swallowEvent = false;
1526 int xm = _mouse->x();
1527 int ym = _mouse->y();
1528 revertTransforms(xm, ym);
1530 DOM::NodeImpl::MouseEvent mev( _mouse->buttons(), DOM::NodeImpl::MouseRelease );
1532 if ( m_part->xmlDocImpl() )
1534 m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev );
1536 DOM::NodeImpl* target = mev.innerNode.handle();
1537 DOM::NodeImpl* fn = m_part->xmlDocImpl()->focusNode();
1539 // a widget may be the real target of this event (e.g. if a scrollbar's slider is being moved)
1540 if (d->m_mouseEventsTarget && fn && fn->renderer() && fn->renderer()->isWidget())
1541 target = fn;
1543 swallowEvent = dispatchMouseEvent(EventImpl::MOUSEUP_EVENT,target,mev.innerNonSharedNode.handle(),true,
1544 d->clickCount,_mouse,false,DOM::NodeImpl::MouseRelease);
1546 // clear our sticky event target on any mouseRelease event
1547 if (d->m_mouseEventsTarget)
1548 d->m_mouseEventsTarget = 0;
1550 if (d->clickCount > 0 &&
1551 QPoint(d->clickX-xm,d->clickY-ym).manhattanLength() <= QApplication::startDragDistance()) {
1552 QMouseEvent me(d->isDoubleClick ? QEvent::MouseButtonDblClick : QEvent::MouseButtonRelease,
1553 _mouse->pos(), _mouse->button(), _mouse->buttons(), _mouse->modifiers());
1554 dispatchMouseEvent(EventImpl::CLICK_EVENT, mev.innerNode.handle(),mev.innerNonSharedNode.handle(),true,
1555 d->clickCount, &me, true, DOM::NodeImpl::MouseRelease);
1558 khtml::RenderObject* r = target ? target->renderer() : 0;
1559 if (r && r->isWidget())
1560 _mouse->ignore();
1563 if (!swallowEvent) {
1564 khtml::MouseReleaseEvent event( _mouse, xm, ym, mev.url, mev.target, mev.innerNode );
1565 QApplication::sendEvent( m_part, &event );
1569 // returns true if event should be swallowed
1570 bool KHTMLView::dispatchKeyEvent( QKeyEvent *_ke )
1572 if (!m_part->xmlDocImpl())
1573 return false;
1574 // Pressing and releasing a key should generate keydown, keypress and keyup events
1575 // Holding it down should generated keydown, keypress (repeatedly) and keyup events
1576 // The problem here is that Qt generates two autorepeat events (keyrelease+keypress)
1577 // for autorepeating, while DOM wants only one autorepeat event (keypress), so one
1578 // of the Qt events shouldn't be passed to DOM, but it should be still filtered
1579 // out if DOM would filter the autorepeat event. Additional problem is that Qt keyrelease
1580 // events don't have text() set (Qt bug?), so DOM often would ignore the keypress event
1581 // if it was created using Qt keyrelease, but Qt autorepeat keyrelease comes
1582 // before Qt autorepeat keypress (i.e. problem whether to filter it out or not).
1583 // The solution is to filter out and postpone the Qt autorepeat keyrelease until
1584 // the following Qt keypress event comes. If DOM accepts the DOM keypress event,
1585 // the postponed event will be simply discarded. If not, it will be passed to keyPressEvent()
1586 // again, and here it will be ignored.
1588 // Qt: Press | Release(autorepeat) Press(autorepeat) etc. | Release
1589 // DOM: Down + Press | (nothing) Press | Up
1591 // It's also possible to get only Releases. E.g. the release of alt-tab,
1592 // or when the keypresses get captured by an accel.
1594 if( _ke == d->postponed_autorepeat ) // replayed event
1596 return false;
1599 if( _ke->type() == QEvent::KeyPress )
1601 if( !_ke->isAutoRepeat())
1603 bool ret = dispatchKeyEventHelper( _ke, false ); // keydown
1604 // don't send keypress even if keydown was blocked, like IE (and unlike Mozilla)
1605 if( !ret && dispatchKeyEventHelper( _ke, true )) // keypress
1606 ret = true;
1607 return ret;
1609 else // autorepeat
1611 bool ret = dispatchKeyEventHelper( _ke, true ); // keypress
1612 if( !ret && d->postponed_autorepeat )
1613 keyPressEvent( d->postponed_autorepeat );
1614 delete d->postponed_autorepeat;
1615 d->postponed_autorepeat = NULL;
1616 return ret;
1619 else // QEvent::KeyRelease
1621 // Discard postponed "autorepeat key-release" events that didn't see
1622 // a keypress after them (e.g. due to QAccel)
1623 if ( d->postponed_autorepeat ) {
1624 delete d->postponed_autorepeat;
1625 d->postponed_autorepeat = 0;
1628 if( !_ke->isAutoRepeat()) {
1629 return dispatchKeyEventHelper( _ke, false ); // keyup
1631 else
1633 d->postponed_autorepeat = new QKeyEvent( _ke->type(), _ke->key(), _ke->modifiers(),
1634 _ke->text(), _ke->isAutoRepeat(), _ke->count());
1635 if( _ke->isAccepted())
1636 d->postponed_autorepeat->accept();
1637 else
1638 d->postponed_autorepeat->ignore();
1639 return true;
1644 // returns true if event should be swallowed
1645 bool KHTMLView::dispatchKeyEventHelper( QKeyEvent *_ke, bool keypress )
1647 DOM::NodeImpl* keyNode = m_part->xmlDocImpl()->focusNode();
1648 if (keyNode) {
1649 return keyNode->dispatchKeyEvent(_ke, keypress);
1650 } else { // no focused node, send to document
1651 return m_part->xmlDocImpl()->dispatchKeyEvent(_ke, keypress);
1655 void KHTMLView::keyPressEvent( QKeyEvent *_ke )
1657 #ifndef KHTML_NO_TYPE_AHEAD_FIND
1658 if(d->typeAheadActivated)
1660 // type-ahead find aka find-as-you-type
1661 if(_ke->key() == Qt::Key_Backspace)
1663 d->findString = d->findString.left(d->findString.length() - 1);
1665 if(!d->findString.isEmpty())
1667 findAhead(false);
1669 else
1671 findTimeout();
1674 d->timer.setSingleShot(true);
1675 d->timer.start(3000);
1676 _ke->accept();
1677 return;
1679 else if(_ke->key() == Qt::Key_Escape)
1681 findTimeout();
1683 _ke->accept();
1684 return;
1686 else if(_ke->key() == Qt::Key_Space || !_ke->text().trimmed().isEmpty())
1688 d->findString += _ke->text();
1690 findAhead(true);
1692 d->timer.setSingleShot(true);
1693 d->timer.start(3000);
1694 _ke->accept();
1695 return;
1698 #endif // KHTML_NO_TYPE_AHEAD_FIND
1700 // If CTRL was hit, be prepared for access keys
1701 if (d->accessKeysEnabled && _ke->key() == Qt::Key_Control && !(_ke->modifiers() & ~Qt::ControlModifier) && !d->accessKeysActivated)
1703 d->accessKeysPreActivate=true;
1704 _ke->accept();
1705 return;
1708 if (_ke->key() == Qt::Key_Shift && !(_ke->modifiers() & ~Qt::ShiftModifier))
1709 d->scrollSuspendPreActivate=true;
1711 // accesskey handling needs to be done before dispatching, otherwise e.g. lineedits
1712 // may eat the event
1714 if (d->accessKeysEnabled && d->accessKeysActivated)
1716 int state = ( _ke->modifiers() & ( Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier ));
1717 if ( state==0 || state==Qt::ShiftModifier) {
1718 if (_ke->key() != Qt::Key_Shift) accessKeysTimeout();
1719 handleAccessKey( _ke );
1720 _ke->accept();
1721 return;
1723 accessKeysTimeout();
1726 if ( dispatchKeyEvent( _ke )) {
1727 // If either keydown or keypress was accepted by a widget, or canceled by JS, stop here.
1728 _ke->accept();
1729 return;
1732 int offs = (viewport()->height() < 30) ? viewport()->height() : 30; // ### ??
1733 if (_ke->modifiers() & Qt::ShiftModifier)
1734 switch(_ke->key())
1736 case Qt::Key_Space:
1737 verticalScrollBar()->setValue( verticalScrollBar()->value() -viewport()->height() + offs );
1738 if(d->scrollSuspended)
1739 d->newScrollTimer(this, 0);
1740 break;
1742 case Qt::Key_Down:
1743 case Qt::Key_J:
1744 d->adjustScroller(this, KHTMLViewPrivate::ScrollDown, KHTMLViewPrivate::ScrollUp);
1745 break;
1747 case Qt::Key_Up:
1748 case Qt::Key_K:
1749 d->adjustScroller(this, KHTMLViewPrivate::ScrollUp, KHTMLViewPrivate::ScrollDown);
1750 break;
1752 case Qt::Key_Left:
1753 case Qt::Key_H:
1754 d->adjustScroller(this, KHTMLViewPrivate::ScrollLeft, KHTMLViewPrivate::ScrollRight);
1755 break;
1757 case Qt::Key_Right:
1758 case Qt::Key_L:
1759 d->adjustScroller(this, KHTMLViewPrivate::ScrollRight, KHTMLViewPrivate::ScrollLeft);
1760 break;
1762 else
1763 switch ( _ke->key() )
1765 case Qt::Key_Down:
1766 case Qt::Key_J:
1767 if (!d->scrollTimerId || d->scrollSuspended)
1768 verticalScrollBar()->setValue( verticalScrollBar()->value()+10 );
1769 if (d->scrollTimerId)
1770 d->newScrollTimer(this, 0);
1771 break;
1773 case Qt::Key_Space:
1774 case Qt::Key_PageDown:
1775 d->shouldSmoothScroll = true;
1776 verticalScrollBar()->setValue( verticalScrollBar()->value() +viewport()->height() - offs );
1777 if(d->scrollSuspended)
1778 d->newScrollTimer(this, 0);
1779 break;
1781 case Qt::Key_Up:
1782 case Qt::Key_K:
1783 if (!d->scrollTimerId || d->scrollSuspended)
1784 verticalScrollBar()->setValue( verticalScrollBar()->value()-10 );
1785 if (d->scrollTimerId)
1786 d->newScrollTimer(this, 0);
1787 break;
1789 case Qt::Key_PageUp:
1790 d->shouldSmoothScroll = true;
1791 verticalScrollBar()->setValue( verticalScrollBar()->value() -viewport()->height() + offs );
1792 if(d->scrollSuspended)
1793 d->newScrollTimer(this, 0);
1794 break;
1795 case Qt::Key_Right:
1796 case Qt::Key_L:
1797 if (!d->scrollTimerId || d->scrollSuspended)
1798 horizontalScrollBar()->setValue( horizontalScrollBar()->value()+10 );
1799 if (d->scrollTimerId)
1800 d->newScrollTimer(this, 0);
1801 break;
1803 case Qt::Key_Left:
1804 case Qt::Key_H:
1805 if (!d->scrollTimerId || d->scrollSuspended)
1806 horizontalScrollBar()->setValue( horizontalScrollBar()->value()-10 );
1807 if (d->scrollTimerId)
1808 d->newScrollTimer(this, 0);
1809 break;
1810 case Qt::Key_Enter:
1811 case Qt::Key_Return:
1812 // ### FIXME:
1813 // or even better to HTMLAnchorElementImpl::event()
1814 if (m_part->xmlDocImpl()) {
1815 NodeImpl *n = m_part->xmlDocImpl()->focusNode();
1816 if (n)
1817 n->setActive();
1819 break;
1820 case Qt::Key_Home:
1821 verticalScrollBar()->setValue( 0 );
1822 horizontalScrollBar()->setValue( 0 );
1823 if(d->scrollSuspended)
1824 d->newScrollTimer(this, 0);
1825 break;
1826 case Qt::Key_End:
1827 verticalScrollBar()->setValue( contentsHeight() - visibleHeight() );
1828 if(d->scrollSuspended)
1829 d->newScrollTimer(this, 0);
1830 break;
1831 case Qt::Key_Shift:
1832 // what are you doing here?
1833 _ke->ignore();
1834 return;
1835 default:
1836 if (d->scrollTimerId)
1837 d->newScrollTimer(this, 0);
1838 _ke->ignore();
1839 return;
1842 _ke->accept();
1845 void KHTMLView::findTimeout()
1847 #ifndef KHTML_NO_TYPE_AHEAD_FIND
1848 d->typeAheadActivated = false;
1849 d->findString = "";
1850 m_part->setStatusBarText(i18n("Find stopped."), KHTMLPart::BarDefaultText);
1851 m_part->enableFindAheadActions( true );
1852 #endif // KHTML_NO_TYPE_AHEAD_FIND
1855 #ifndef KHTML_NO_TYPE_AHEAD_FIND
1856 void KHTMLView::startFindAhead( bool linksOnly )
1858 if( linksOnly )
1860 d->findLinksOnly = true;
1861 m_part->setStatusBarText(i18n("Starting -- find links as you type"),
1862 KHTMLPart::BarDefaultText);
1864 else
1866 d->findLinksOnly = false;
1867 m_part->setStatusBarText(i18n("Starting -- find text as you type"),
1868 KHTMLPart::BarDefaultText);
1871 m_part->findTextBegin();
1872 d->typeAheadActivated = true;
1873 // disable, so that the shortcut ( / or ' by default ) doesn't interfere
1874 m_part->enableFindAheadActions( false );
1875 d->timer.setSingleShot(true);
1876 d->timer.start(3000);
1879 void KHTMLView::findAhead(bool increase)
1881 QString status;
1882 QString text = d->findString.toLower();
1884 if(d->findLinksOnly)
1886 m_part->findText(d->findString, KHTMLPart::FindNoPopups |
1887 KHTMLPart::FindLinksOnly, this);
1888 if(m_part->findTextNext())
1890 status = i18n("Link found: \"%1\".", Qt::escape(text));
1892 else
1894 if(increase) KNotification::beep();
1895 status = i18n("Link not found: \"%1\".", Qt::escape(text));
1898 else
1900 m_part->findText(d->findString, KHTMLPart::FindNoPopups, this);
1901 if(m_part->findTextNext())
1903 status = i18n("Text found: \"%1\".", Qt::escape(text));
1905 else
1907 if(increase) KNotification::beep();
1908 status = i18n("Text not found: \"%1\".", Qt::escape(text));
1912 // Note: we need to escape -twice-: the above just escape for i18n, now we need to do it for Qt, too.
1913 m_part->setStatusBarText(Qt::escape(status), KHTMLPart::BarDefaultText);
1916 void KHTMLView::updateFindAheadTimeout()
1918 if( d->typeAheadActivated ) {
1919 d->timer.setSingleShot( true );
1920 d->timer.start( 3000 );
1924 #endif // KHTML_NO_TYPE_AHEAD_FIND
1926 void KHTMLView::keyReleaseEvent(QKeyEvent *_ke)
1928 #ifndef KHTML_NO_TYPE_AHEAD_FIND
1929 if(d->typeAheadActivated) {
1930 _ke->accept();
1931 return;
1933 #endif
1935 if( d->scrollSuspendPreActivate && _ke->key() != Qt::Key_Shift )
1936 d->scrollSuspendPreActivate = false;
1937 if( _ke->key() == Qt::Key_Shift && d->scrollSuspendPreActivate && !(_ke->modifiers() & Qt::ShiftModifier))
1938 if (d->scrollTimerId) {
1939 d->scrollSuspended = !d->scrollSuspended;
1940 if (d->scrollSuspended)
1941 d->stopScrolling();
1944 if (d->accessKeysEnabled)
1946 if (d->accessKeysPreActivate && _ke->key() != Qt::Key_Control)
1947 d->accessKeysPreActivate=false;
1948 if (d->accessKeysPreActivate && !(_ke->modifiers() & Qt::ControlModifier))
1950 displayAccessKeys();
1951 m_part->setStatusBarText(i18n("Access Keys activated"),KHTMLPart::BarOverrideText);
1952 d->accessKeysActivated = true;
1953 d->accessKeysPreActivate = false;
1954 _ke->accept();
1955 return;
1957 else if (d->accessKeysActivated)
1959 accessKeysTimeout();
1960 _ke->accept();
1961 return;
1965 // Send keyup event
1966 if ( dispatchKeyEvent( _ke ) )
1968 _ke->accept();
1969 return;
1972 QScrollArea::keyReleaseEvent(_ke);
1975 bool KHTMLView::focusNextPrevChild( bool next )
1977 // Now try to find the next child
1978 if (m_part->xmlDocImpl() && focusNextPrevNode(next))
1980 if (m_part->xmlDocImpl()->focusNode())
1981 kDebug() << "focusNode.name: "
1982 << m_part->xmlDocImpl()->focusNode()->nodeName().string() << endl;
1983 return true; // focus node found
1986 // If we get here, pass tabbing control up to the next/previous child in our parent
1987 d->pseudoFocusNode = KHTMLViewPrivate::PFNone;
1988 if (m_part->parentPart() && m_part->parentPart()->view())
1989 return m_part->parentPart()->view()->focusNextPrevChild(next);
1991 return QWidget::focusNextPrevChild(next);
1994 void KHTMLView::doAutoScroll()
1996 QPoint pos = QCursor::pos();
1997 QPoint off;
1998 KHTMLView* v = m_kwp->isRedirected() ? m_kwp->rootViewPos(off) : this;
1999 pos = v->viewport()->mapFromGlobal( pos );
2000 pos -= off;
2001 int xm, ym;
2002 viewportToContents(pos.x(), pos.y(), xm, ym); // ###
2004 pos = QPoint(pos.x() - viewport()->x(), pos.y() - viewport()->y());
2005 if ( (pos.y() < 0) || (pos.y() > visibleHeight()) ||
2006 (pos.x() < 0) || (pos.x() > visibleWidth()) )
2008 ensureVisible( xm, ym, 0, 5 );
2010 #ifndef KHTML_NO_SELECTION
2011 // extend the selection while scrolling
2012 DOM::Node innerNode;
2013 if (m_part->isExtendingSelection()) {
2014 RenderObject::NodeInfo renderInfo(true/*readonly*/, false/*active*/);
2015 m_part->xmlDocImpl()->renderer()->layer()
2016 ->nodeAtPoint(renderInfo, xm, ym);
2017 innerNode = renderInfo.innerNode();
2018 }/*end if*/
2020 if (innerNode.handle() && innerNode.handle()->renderer()
2021 && innerNode.handle()->renderer()->shouldSelect()) {
2022 m_part->extendSelectionTo(xm, ym, innerNode);
2023 }/*end if*/
2024 #endif // KHTML_NO_SELECTION
2028 // KHTML defines its own stacking order for any object and thus takes
2029 // control of widget painting whenever it can. This is called "redirection".
2031 // Redirected widgets are placed off screen. When they are declared as a child of our view (ChildPolished event),
2032 // an event filter is installed, so as to catch any paint event and translate them as update() of the view's main widget.
2034 // Painting also happens spontaneously within widgets. In this case, the widget would update() parts of itself.
2035 // While this ordinarily results in a paintEvent being schedduled, it is not the case with off screen widgets.
2036 // Thus update() is monitored by using the mechanism that deffers any update call happening during a paint event,
2037 // transforming it into a posted UpdateLater event. Hence the need to set Qt::WA_WState_InPaintEvent on redirected widgets.
2039 // Once the UpdateLater event has been received, Qt::WA_WState_InPaintEvent is removed and the process continues
2040 // with the update of the corresponding rect on the view. That in turn will make our painting subsystem render()
2041 // the widget at the correct stacking position.
2043 // For non-redirected (e.g. external) widgets, z-order is honoured through masking. cf.RenderLayer::updateWidgetMasks
2045 static void handleWidget(QWidget* w, KHTMLView* view, bool recurse=true)
2047 if (w->isWindow())
2048 return;
2050 if (!qobject_cast<QFrame*>(w))
2051 w->setAttribute( Qt::WA_NoSystemBackground );
2053 w->setAttribute(Qt::WA_WState_InPaintEvent);
2054 w->setAttribute(Qt::WA_OpaquePaintEvent);
2055 w->installEventFilter(view);
2057 if (!recurse)
2058 return;
2059 if (qobject_cast<KHTMLView*>(w)) {
2060 handleWidget(static_cast<KHTMLView*>(w)->widget(), view, false);
2061 handleWidget(static_cast<KHTMLView*>(w)->horizontalScrollBar(), view, false);
2062 handleWidget(static_cast<KHTMLView*>(w)->verticalScrollBar(), view, false);
2063 return;
2066 QObjectList children = w->children();
2067 foreach (QObject* object, children) {
2068 QWidget *widget = qobject_cast<QWidget*>(object);
2069 if (widget)
2070 handleWidget(widget, view);
2074 class KHTMLBackingStoreHackWidget : public QWidget
2076 public:
2077 void publicEvent(QEvent *e)
2079 QWidget::event(e);
2083 bool KHTMLView::viewportEvent ( QEvent * e )
2085 switch (e->type()) {
2086 // those must not be dispatched to the specialized handlers
2087 // as widgetEvent() already took care of that
2088 case QEvent::MouseButtonPress:
2089 case QEvent::MouseButtonRelease:
2090 case QEvent::MouseButtonDblClick:
2091 case QEvent::MouseMove:
2092 #ifndef QT_NO_WHEELEVENT
2093 case QEvent::Wheel:
2094 #endif
2095 case QEvent::ContextMenu:
2096 case QEvent::DragEnter:
2097 case QEvent::DragMove:
2098 case QEvent::DragLeave:
2099 case QEvent::Drop:
2100 return false;
2101 case QEvent::Paint: {
2102 QRect r = static_cast<QPaintEvent*>(e)->rect();
2103 r.setX(r.x() +contentsX());
2104 r.setY(r.y() +contentsY());
2105 QPaintEvent pe(r);
2106 paintEvent(&pe);
2107 return true;
2109 default:
2110 break;
2112 return QScrollArea::viewportEvent(e);
2115 static void setInPaintEventFlag(QWidget* w, bool b = true, bool recurse=true)
2117 w->setAttribute(Qt::WA_WState_InPaintEvent, b);
2119 if (!recurse)
2120 return;
2121 if (qobject_cast<KHTMLView*>(w)) {
2122 setInPaintEventFlag(static_cast<KHTMLView*>(w)->widget(), b, false);
2123 setInPaintEventFlag(static_cast<KHTMLView*>(w)->horizontalScrollBar(), b, false);
2124 setInPaintEventFlag(static_cast<KHTMLView*>(w)->verticalScrollBar(), b, false);
2125 return;
2128 foreach(QObject* cw, w->children()) {
2129 if (cw->isWidgetType() && ! static_cast<QWidget*>(cw)->isWindow()
2130 && !(static_cast<QWidget*>(cw)->windowModality() & Qt::ApplicationModal)) {
2131 setInPaintEventFlag(static_cast<QWidget*>(cw), b);
2136 bool KHTMLView::eventFilter(QObject *o, QEvent *e)
2138 if ( e->type() == QEvent::ShortcutOverride ) {
2139 QKeyEvent* ke = (QKeyEvent*) e;
2140 if (m_part->isEditable() || m_part->isCaretMode()
2141 || (m_part->xmlDocImpl() && m_part->xmlDocImpl()->focusNode()
2142 && m_part->xmlDocImpl()->focusNode()->isContentEditable())) {
2143 if ( (ke->modifiers() & Qt::ControlModifier) || (ke->modifiers() & Qt::ShiftModifier) ) {
2144 switch ( ke->key() ) {
2145 case Qt::Key_Left:
2146 case Qt::Key_Right:
2147 case Qt::Key_Up:
2148 case Qt::Key_Down:
2149 case Qt::Key_Home:
2150 case Qt::Key_End:
2151 ke->accept();
2152 return true;
2153 default:
2154 break;
2160 if ( e->type() == QEvent::Leave ) {
2161 if ( d->cursorIconWidget )
2162 d->cursorIconWidget->hide();
2163 m_part->resetHoverText();
2166 QWidget *view = widget();
2167 if (o == view) {
2168 if (widgetEvent(e))
2169 return true;
2170 else if (e->type() == QEvent::Resize) {
2171 updateScrollBars();
2172 return false;
2174 } else if (o->isWidgetType()) {
2175 QWidget *v = static_cast<QWidget *>(o);
2176 QWidget *c = v;
2177 while (v && v != view) {
2178 c = v;
2179 v = v->parentWidget();
2181 KHTMLWidget* k = dynamic_cast<KHTMLWidget*>(c);
2182 if (v && k && k->m_kwp->isRedirected()) {
2183 bool block = false;
2184 bool isUpdate = false;
2185 QWidget *w = static_cast<QWidget *>(o);
2186 switch(e->type()) {
2187 case QEvent::UpdateRequest: {
2188 // implicitly call qt_syncBackingStore(w)
2189 static_cast<KHTMLBackingStoreHackWidget *>(w)->publicEvent(e);
2190 block = true;
2191 break;
2193 case QEvent::UpdateLater:
2194 isUpdate = true;
2195 // no break;
2196 case QEvent::Paint:
2197 if (!allowWidgetPaintEvents) {
2198 // eat the event. Like this we can control exactly when the widget
2199 // gets repainted.
2200 block = true;
2201 int x = 0, y = 0;
2202 QWidget *v = w;
2203 while (v && v->parentWidget() != view) {
2204 x += v->x();
2205 y += v->y();
2206 v = v->parentWidget();
2209 QPoint ap = k->m_kwp->absolutePos();
2210 x += ap.x();
2211 y += ap.y();
2213 QRect pr = isUpdate ? static_cast<QUpdateLaterEvent*>(e)->region().boundingRect() : static_cast<QPaintEvent*>(e)->rect();
2214 bool asap = !d->contentsMoving && qobject_cast<QAbstractScrollArea*>(c);
2216 if (isUpdate) {
2217 setInPaintEventFlag(w, false);
2218 if (asap)
2219 w->repaint(static_cast<QUpdateLaterEvent*>(e)->region());
2220 else
2221 w->update(static_cast<QUpdateLaterEvent*>(e)->region());
2222 setInPaintEventFlag(w);
2225 // QScrollView needs fast repaints
2226 if ( asap && !isUpdate && !d->painting && m_part->xmlDocImpl() && m_part->xmlDocImpl()->renderer() &&
2227 !static_cast<khtml::RenderCanvas *>(m_part->xmlDocImpl()->renderer())->needsLayout() ) {
2228 repaintContents(x + pr.x(), y + pr.y(),
2229 pr.width(), pr.height()+1); // ### investigate that +1 (shows up when
2230 // updating e.g a textarea's blinking cursor)
2231 } else if (!d->painting) {
2232 scheduleRepaint(x + pr.x(), y + pr.y(),
2233 pr.width(), pr.height()+1, asap);
2236 break;
2237 case QEvent::MouseMove:
2238 case QEvent::MouseButtonPress:
2239 case QEvent::MouseButtonRelease:
2240 case QEvent::MouseButtonDblClick: {
2242 if (0 && w->parentWidget() == view && !qobject_cast<QScrollBar*>(w) && !::qobject_cast<QScrollBar *>(w)) {
2243 QMouseEvent *me = static_cast<QMouseEvent *>(e);
2244 QPoint pt = w->mapTo( view, me->pos());
2245 QMouseEvent me2(me->type(), pt, me->button(), me->buttons(), me->modifiers());
2247 if (e->type() == QEvent::MouseMove)
2248 mouseMoveEvent(&me2);
2249 else if(e->type() == QEvent::MouseButtonPress)
2250 mousePressEvent(&me2);
2251 else if(e->type() == QEvent::MouseButtonRelease)
2252 mouseReleaseEvent(&me2);
2253 else
2254 mouseDoubleClickEvent(&me2);
2255 block = true;
2257 break;
2259 case QEvent::KeyPress:
2260 case QEvent::KeyRelease:
2261 if (w->parentWidget() == view && !qobject_cast<QScrollBar*>(w)) {
2262 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
2263 if (e->type() == QEvent::KeyPress)
2264 keyPressEvent(ke);
2265 else
2266 keyReleaseEvent(ke);
2267 block = true;
2269 break;
2270 case QEvent::FocusIn:
2271 case QEvent::FocusOut:
2272 block = true;
2273 break;
2274 default:
2275 break;
2277 if (block) {
2278 //qDebug("eating event");
2279 return true;
2284 // kDebug(6000) <<"passing event on to sv event filter object=" << o->className() << " event=" << e->type();
2285 return QScrollArea::eventFilter(o, e);
2288 bool KHTMLView::widgetEvent(QEvent* e)
2290 switch (e->type()) {
2291 case QEvent::MouseButtonPress:
2292 case QEvent::MouseButtonRelease:
2293 case QEvent::MouseButtonDblClick:
2294 case QEvent::MouseMove:
2295 case QEvent::Paint:
2296 #ifndef QT_NO_WHEELEVENT
2297 case QEvent::Wheel:
2298 #endif
2299 case QEvent::ContextMenu:
2300 case QEvent::DragEnter:
2301 case QEvent::DragMove:
2302 case QEvent::DragLeave:
2303 case QEvent::Drop:
2304 return QFrame::event(e);
2305 case QEvent::ChildPolished: {
2306 // we need to install an event filter on all children of the widget() to
2307 // be able to get correct stacking of children within the document.
2308 QObject *c = static_cast<QChildEvent *>(e)->child();
2309 if (c->isWidgetType()) {
2310 QWidget *w = static_cast<QWidget *>(c);
2311 // don't install the event filter on toplevels
2312 if (!(w->windowFlags() & Qt::Window) && !(w->windowModality() & Qt::ApplicationModal)) {
2313 KHTMLWidget* k = dynamic_cast<KHTMLWidget*>(w);
2314 if (k && k->m_kwp->isRedirected()) {
2315 w->unsetCursor();
2316 handleWidget(w, this);
2321 case QEvent::Move: {
2322 if (static_cast<QMoveEvent*>(e)->pos() != QPoint(0,0)) {
2323 widget()->move(0,0);
2324 updateScrollBars();
2325 return true;
2328 default:
2329 break;
2331 return false;
2334 DOM::NodeImpl *KHTMLView::nodeUnderMouse() const
2336 return d->underMouse;
2339 DOM::NodeImpl *KHTMLView::nonSharedNodeUnderMouse() const
2341 return d->underMouseNonShared;
2344 bool KHTMLView::scrollTo(const QRect &bounds)
2346 d->scrollingSelf = true; // so scroll events get ignored
2348 int x, y, xe, ye;
2349 x = bounds.left();
2350 y = bounds.top();
2351 xe = bounds.right();
2352 ye = bounds.bottom();
2354 //kDebug(6000)<<"scrolling coords: x="<<x<<" y="<<y<<" width="<<xe-x<<" height="<<ye-y;
2356 int deltax;
2357 int deltay;
2359 int curHeight = visibleHeight();
2360 int curWidth = visibleWidth();
2362 if (ye-y>curHeight-d->borderY)
2363 ye = y + curHeight - d->borderY;
2365 if (xe-x>curWidth-d->borderX)
2366 xe = x + curWidth - d->borderX;
2368 // is xpos of target left of the view's border?
2369 if (x < contentsX() + d->borderX )
2370 deltax = x - contentsX() - d->borderX;
2371 // is xpos of target right of the view's right border?
2372 else if (xe + d->borderX > contentsX() + curWidth)
2373 deltax = xe + d->borderX - ( contentsX() + curWidth );
2374 else
2375 deltax = 0;
2377 // is ypos of target above upper border?
2378 if (y < contentsY() + d->borderY)
2379 deltay = y - contentsY() - d->borderY;
2380 // is ypos of target below lower border?
2381 else if (ye + d->borderY > contentsY() + curHeight)
2382 deltay = ye + d->borderY - ( contentsY() + curHeight );
2383 else
2384 deltay = 0;
2386 int maxx = curWidth-d->borderX;
2387 int maxy = curHeight-d->borderY;
2389 int scrollX, scrollY;
2391 scrollX = deltax > 0 ? (deltax > maxx ? maxx : deltax) : deltax == 0 ? 0 : (deltax>-maxx ? deltax : -maxx);
2392 scrollY = deltay > 0 ? (deltay > maxy ? maxy : deltay) : deltay == 0 ? 0 : (deltay>-maxy ? deltay : -maxy);
2394 if (contentsX() + scrollX < 0)
2395 scrollX = -contentsX();
2396 else if (contentsWidth() - visibleWidth() - contentsX() < scrollX)
2397 scrollX = contentsWidth() - visibleWidth() - contentsX();
2399 if (contentsY() + scrollY < 0)
2400 scrollY = -contentsY();
2401 else if (contentsHeight() - visibleHeight() - contentsY() < scrollY)
2402 scrollY = contentsHeight() - visibleHeight() - contentsY();
2404 horizontalScrollBar()->setValue( horizontalScrollBar()->value()+scrollX );
2405 verticalScrollBar()->setValue( verticalScrollBar()->value()+scrollY );
2407 d->scrollingSelf = false;
2409 if ( (abs(deltax)<=maxx) && (abs(deltay)<=maxy) )
2410 return true;
2411 else return false;
2415 bool KHTMLView::focusNextPrevNode(bool next)
2417 // Sets the focus node of the document to be the node after (or if
2418 // next is false, before) the current focus node. Only nodes that
2419 // are selectable (i.e. for which isFocusable() returns true) are
2420 // taken into account, and the order used is that specified in the
2421 // HTML spec (see DocumentImpl::nextFocusNode() and
2422 // DocumentImpl::previousFocusNode() for details).
2424 DocumentImpl *doc = m_part->xmlDocImpl();
2425 NodeImpl *oldFocusNode = doc->focusNode();
2427 // See whether we're in the middle of a detach, or hiding of the
2428 // widget. In this case, we will just clear focus, being careful not to emit events
2429 // or update rendering. Doing this also prevents the code below from going bonkers with
2430 // oldFocusNode not actually being focusable, etc.
2431 if (oldFocusNode) {
2432 if ((oldFocusNode->renderer() && !oldFocusNode->renderer()->parent())
2433 || !oldFocusNode->isTabFocusable()) {
2434 doc->quietResetFocus();
2435 return true;
2439 #if 1
2440 // If the user has scrolled the document, then instead of picking
2441 // the next focusable node in the document, use the first one that
2442 // is within the visible area (if possible).
2443 if (d->scrollBarMoved)
2445 NodeImpl *toFocus;
2446 if (next)
2447 toFocus = doc->nextFocusNode(oldFocusNode);
2448 else
2449 toFocus = doc->previousFocusNode(oldFocusNode);
2451 if (!toFocus && oldFocusNode) {
2452 if (next)
2453 toFocus = doc->nextFocusNode(NULL);
2454 else
2455 toFocus = doc->previousFocusNode(NULL);
2458 while (toFocus && toFocus != oldFocusNode)
2461 QRect focusNodeRect = toFocus->getRect();
2462 if ((focusNodeRect.left() > contentsX()) && (focusNodeRect.right() < contentsX() + visibleWidth()) &&
2463 (focusNodeRect.top() > contentsY()) && (focusNodeRect.bottom() < contentsY() + visibleHeight())) {
2465 QRect r = toFocus->getRect();
2466 ensureVisible( r.right(), r.bottom());
2467 ensureVisible( r.left(), r.top());
2468 d->scrollBarMoved = false;
2469 d->tabMovePending = false;
2470 d->lastTabbingDirection = next;
2471 d->pseudoFocusNode = KHTMLViewPrivate::PFNone;
2472 m_part->xmlDocImpl()->setFocusNode(toFocus);
2473 Node guard(toFocus);
2474 if (!toFocus->hasOneRef() )
2476 emit m_part->nodeActivated(Node(toFocus));
2478 return true;
2481 if (next)
2482 toFocus = doc->nextFocusNode(toFocus);
2483 else
2484 toFocus = doc->previousFocusNode(toFocus);
2486 if (!toFocus && oldFocusNode)
2488 if (next)
2490 toFocus = doc->nextFocusNode(NULL);
2492 else
2494 toFocus = doc->previousFocusNode(NULL);
2499 d->scrollBarMoved = false;
2501 #endif
2503 if (!oldFocusNode && d->pseudoFocusNode == KHTMLViewPrivate::PFNone)
2505 ensureVisible(contentsX(), next?0:contentsHeight());
2506 d->scrollBarMoved = false;
2507 d->pseudoFocusNode = next?KHTMLViewPrivate::PFTop:KHTMLViewPrivate::PFBottom;
2508 return true;
2511 NodeImpl *newFocusNode = NULL;
2513 if (d->tabMovePending && next != d->lastTabbingDirection)
2515 //kDebug ( 6000 ) << " tab move pending and tabbing direction changed!\n";
2516 newFocusNode = oldFocusNode;
2518 else if (next)
2520 if (oldFocusNode || d->pseudoFocusNode == KHTMLViewPrivate::PFTop )
2521 newFocusNode = doc->nextFocusNode(oldFocusNode);
2523 else
2525 if (oldFocusNode || d->pseudoFocusNode == KHTMLViewPrivate::PFBottom )
2526 newFocusNode = doc->previousFocusNode(oldFocusNode);
2529 bool targetVisible = false;
2530 if (!newFocusNode)
2532 if ( next )
2534 targetVisible = scrollTo(QRect(contentsX()+visibleWidth()/2,contentsHeight()-d->borderY,0,0));
2536 else
2538 targetVisible = scrollTo(QRect(contentsX()+visibleWidth()/2,d->borderY,0,0));
2541 else
2543 // if it's an editable element, activate the caret
2544 if (!m_part->isCaretMode() && newFocusNode->isContentEditable()) {
2545 kDebug(6200) << "show caret! fn: " << newFocusNode->nodeName().string() << endl;
2546 m_part->clearCaretRectIfNeeded();
2547 m_part->d->editor_context.m_selection.moveTo(Position(newFocusNode, 0L));
2548 m_part->setCaretVisible(true);
2549 } else {
2550 m_part->setCaretVisible(false);
2551 kDebug(6200) << "hide caret! fn: " << newFocusNode->nodeName().string() << endl;
2553 m_part->notifySelectionChanged();
2555 targetVisible = scrollTo(newFocusNode->getRect());
2558 if (targetVisible)
2560 //kDebug ( 6000 ) << " target reached.\n";
2561 d->tabMovePending = false;
2563 m_part->xmlDocImpl()->setFocusNode(newFocusNode);
2564 if (newFocusNode)
2566 Node guard(newFocusNode);
2567 if (!newFocusNode->hasOneRef() )
2569 emit m_part->nodeActivated(Node(newFocusNode));
2571 return true;
2573 else
2575 d->pseudoFocusNode = next?KHTMLViewPrivate::PFBottom:KHTMLViewPrivate::PFTop;
2576 return false;
2579 else
2581 if (!d->tabMovePending)
2582 d->lastTabbingDirection = next;
2583 d->tabMovePending = true;
2584 return true;
2588 void KHTMLView::displayAccessKeys()
2590 QVector< QChar > taken;
2591 displayAccessKeys( NULL, this, taken, false );
2592 displayAccessKeys( NULL, this, taken, true );
2595 void KHTMLView::displayAccessKeys( KHTMLView* caller, KHTMLView* origview, QVector< QChar >& taken, bool use_fallbacks )
2597 QMap< ElementImpl*, QChar > fallbacks;
2598 if( use_fallbacks )
2599 fallbacks = buildFallbackAccessKeys();
2600 for( NodeImpl* n = m_part->xmlDocImpl(); n != NULL; n = n->traverseNextNode()) {
2601 if( n->isElementNode()) {
2602 ElementImpl* en = static_cast< ElementImpl* >( n );
2603 DOMString s = en->getAttribute( ATTR_ACCESSKEY );
2604 QString accesskey;
2605 if( s.length() == 1 ) {
2606 QChar a = s.string()[ 0 ].toUpper();
2607 if( qFind( taken.begin(), taken.end(), a ) == taken.end()) // !contains
2608 accesskey = a;
2610 if( accesskey.isNull() && fallbacks.contains( en )) {
2611 QChar a = fallbacks[ en ].toUpper();
2612 if( qFind( taken.begin(), taken.end(), a ) == taken.end()) // !contains
2613 accesskey = QString( "<qt><i>" ) + a + "</i></qt>";
2615 if( !accesskey.isNull()) {
2616 QRect rec=en->getRect();
2617 QLabel *lab=new QLabel(accesskey,viewport());
2618 lab->setAttribute(Qt::WA_DeleteOnClose);
2619 connect( origview, SIGNAL(hideAccessKeys()), lab, SLOT(close()) );
2620 connect( this, SIGNAL(repaintAccessKeys()), lab, SLOT(repaint()));
2621 lab->setPalette(QToolTip::palette());
2622 lab->setLineWidth(2);
2623 lab->setFrameStyle(QFrame::Box | QFrame::Plain);
2624 lab->setMargin(3);
2625 lab->adjustSize();
2626 lab->setParent( widget() );
2627 lab->setAutoFillBackground(true);
2628 lab->move(
2629 qMin(rec.left()+rec.width()/2 - contentsX(), contentsWidth() - lab->width()),
2630 qMin(rec.top()+rec.height()/2 - contentsY(), contentsHeight() - lab->height()));
2631 lab->show();
2632 taken.append( accesskey[ 0 ] );
2636 if( use_fallbacks )
2637 return;
2639 QList<KParts::ReadOnlyPart*> frames = m_part->frames();
2640 foreach( KParts::ReadOnlyPart* cur, frames ) {
2641 if( !qobject_cast<KHTMLPart*>(cur) )
2642 continue;
2643 KHTMLPart* part = static_cast< KHTMLPart* >( cur );
2644 if( part->view() && part->view() != caller )
2645 part->view()->displayAccessKeys( this, origview, taken, use_fallbacks );
2648 // pass up to the parent
2649 if (m_part->parentPart() && m_part->parentPart()->view()
2650 && m_part->parentPart()->view() != caller)
2651 m_part->parentPart()->view()->displayAccessKeys( this, origview, taken, use_fallbacks );
2654 bool KHTMLView::isScrollingFromMouseWheel() const
2656 return d->scrollingFromWheel != QPoint(-1,-1);
2659 void KHTMLView::accessKeysTimeout()
2661 d->accessKeysActivated=false;
2662 d->accessKeysPreActivate = false;
2663 m_part->setStatusBarText(QString(), KHTMLPart::BarOverrideText);
2664 emit hideAccessKeys();
2667 // Handling of the HTML accesskey attribute.
2668 bool KHTMLView::handleAccessKey( const QKeyEvent* ev )
2670 // Qt interprets the keyevent also with the modifiers, and ev->text() matches that,
2671 // but this code must act as if the modifiers weren't pressed
2672 QChar c;
2673 if( ev->key() >= Qt::Key_A && ev->key() <= Qt::Key_Z )
2674 c = 'A' + ev->key() - Qt::Key_A;
2675 else if( ev->key() >= Qt::Key_0 && ev->key() <= Qt::Key_9 )
2676 c = '0' + ev->key() - Qt::Key_0;
2677 else {
2678 // TODO fake XKeyEvent and XLookupString ?
2679 // This below seems to work e.g. for eacute though.
2680 if( ev->text().length() == 1 )
2681 c = ev->text()[ 0 ];
2683 if( c.isNull())
2684 return false;
2685 return focusNodeWithAccessKey( c );
2688 bool KHTMLView::focusNodeWithAccessKey( QChar c, KHTMLView* caller )
2690 DocumentImpl *doc = m_part->xmlDocImpl();
2691 if( !doc )
2692 return false;
2693 ElementImpl* node = doc->findAccessKeyElement( c );
2694 if( !node ) {
2695 QList<KParts::ReadOnlyPart*> frames = m_part->frames();
2696 foreach( KParts::ReadOnlyPart* cur, frames ) {
2697 if( !qobject_cast<KHTMLPart*>(cur) )
2698 continue;
2699 KHTMLPart* part = static_cast< KHTMLPart* >( cur );
2700 if( part->view() && part->view() != caller
2701 && part->view()->focusNodeWithAccessKey( c, this ))
2702 return true;
2704 // pass up to the parent
2705 if (m_part->parentPart() && m_part->parentPart()->view()
2706 && m_part->parentPart()->view() != caller
2707 && m_part->parentPart()->view()->focusNodeWithAccessKey( c, this ))
2708 return true;
2709 if( caller == NULL ) { // the active frame (where the accesskey was pressed)
2710 const QMap< ElementImpl*, QChar > fallbacks = buildFallbackAccessKeys();
2711 for( QMap< ElementImpl*, QChar >::ConstIterator it = fallbacks.begin();
2712 it != fallbacks.end();
2713 ++it )
2714 if( *it == c ) {
2715 node = it.key();
2716 break;
2719 if( node == NULL )
2720 return false;
2723 // Scroll the view as necessary to ensure that the new focus node is visible
2725 QRect r = node->getRect();
2726 ensureVisible( r.right(), r.bottom());
2727 ensureVisible( r.left(), r.top());
2729 Node guard( node );
2730 if( node->isFocusable()) {
2731 if (node->id()==ID_LABEL) {
2732 // if Accesskey is a label, give focus to the label's referrer.
2733 node=static_cast<ElementImpl *>(static_cast< HTMLLabelElementImpl* >( node )->getFormElement());
2734 if (!node) return true;
2735 guard = node;
2737 // Set focus node on the document
2738 #ifdef __GNUC__
2739 #warning "port QFocusEvent::setReason( QFocusEvent::Shortcut ); to qt4"
2740 #endif
2741 //QFocusEvent::setReason( QFocusEvent::Shortcut );
2742 m_part->xmlDocImpl()->setFocusNode(node);
2743 #ifdef __GNUC__
2744 #warning "port QFocusEvent::resetReason(); to qt4"
2745 #endif
2746 //QFocusEvent::resetReason();
2747 if( node != NULL && node->hasOneRef()) // deleted, only held by guard
2748 return true;
2749 emit m_part->nodeActivated(Node(node));
2750 if( node != NULL && node->hasOneRef())
2751 return true;
2754 switch( node->id()) {
2755 case ID_A:
2756 static_cast< HTMLAnchorElementImpl* >( node )->click();
2757 break;
2758 case ID_INPUT:
2759 static_cast< HTMLInputElementImpl* >( node )->click();
2760 break;
2761 case ID_BUTTON:
2762 static_cast< HTMLButtonElementImpl* >( node )->click();
2763 break;
2764 case ID_AREA:
2765 static_cast< HTMLAreaElementImpl* >( node )->click();
2766 break;
2767 case ID_TEXTAREA:
2768 break; // just focusing it is enough
2769 case ID_LEGEND:
2770 // TODO
2771 break;
2773 return true;
2776 static QString getElementText( NodeImpl* start, bool after )
2778 QString ret; // nextSibling(), to go after e.g. </select>
2779 for( NodeImpl* n = after ? start->nextSibling() : start->traversePreviousNode();
2780 n != NULL;
2781 n = after ? n->traverseNextNode() : n->traversePreviousNode()) {
2782 if( n->isTextNode()) {
2783 if( after )
2784 ret += static_cast< TextImpl* >( n )->toString().string();
2785 else
2786 ret.prepend( static_cast< TextImpl* >( n )->toString().string());
2787 } else {
2788 switch( n->id()) {
2789 case ID_A:
2790 case ID_FONT:
2791 case ID_TT:
2792 case ID_U:
2793 case ID_B:
2794 case ID_I:
2795 case ID_S:
2796 case ID_STRIKE:
2797 case ID_BIG:
2798 case ID_SMALL:
2799 case ID_EM:
2800 case ID_STRONG:
2801 case ID_DFN:
2802 case ID_CODE:
2803 case ID_SAMP:
2804 case ID_KBD:
2805 case ID_VAR:
2806 case ID_CITE:
2807 case ID_ABBR:
2808 case ID_ACRONYM:
2809 case ID_SUB:
2810 case ID_SUP:
2811 case ID_SPAN:
2812 case ID_NOBR:
2813 case ID_WBR:
2814 break;
2815 case ID_TD:
2816 if( ret.trimmed().isEmpty())
2817 break;
2818 // fall through
2819 default:
2820 return ret.simplified();
2824 return ret.simplified();
2827 static QMap< NodeImpl*, QString > buildLabels( NodeImpl* start )
2829 QMap< NodeImpl*, QString > ret;
2830 for( NodeImpl* n = start;
2831 n != NULL;
2832 n = n->traverseNextNode()) {
2833 if( n->id() == ID_LABEL ) {
2834 HTMLLabelElementImpl* label = static_cast< HTMLLabelElementImpl* >( n );
2835 NodeImpl* labelfor = label->getFormElement();
2836 if( labelfor )
2837 ret[ labelfor ] = label->innerText().string().simplified();
2840 return ret;
2843 namespace khtml {
2844 struct AccessKeyData {
2845 ElementImpl* element;
2846 QString text;
2847 QString url;
2848 int priority; // 10(highest) - 0(lowest)
2852 QMap< ElementImpl*, QChar > KHTMLView::buildFallbackAccessKeys() const
2854 // build a list of all possible candidate elements that could use an accesskey
2855 QLinkedList< AccessKeyData > data; // Note: this has to be a list type that keep iterators valid
2856 // when other entries are removed
2857 QMap< NodeImpl*, QString > labels = buildLabels( m_part->xmlDocImpl());
2858 for( NodeImpl* n = m_part->xmlDocImpl();
2859 n != NULL;
2860 n = n->traverseNextNode()) {
2861 if( n->isElementNode()) {
2862 ElementImpl* element = static_cast< ElementImpl* >( n );
2863 if( element->getAttribute( ATTR_ACCESSKEY ).length() == 1 )
2864 continue; // has accesskey set, ignore
2865 if( element->renderer() == NULL )
2866 continue; // not visible
2867 QString text;
2868 QString url;
2869 int priority = 0;
2870 bool ignore = false;
2871 bool text_after = false;
2872 bool text_before = false;
2873 switch( element->id()) {
2874 case ID_A:
2875 url = khtml::parseURL(element->getAttribute(ATTR_HREF)).string();
2876 if( url.isEmpty()) // doesn't have href, it's only an anchor
2877 continue;
2878 text = static_cast< HTMLElementImpl* >( element )->innerText().string().simplified();
2879 priority = 2;
2880 break;
2881 case ID_INPUT: {
2882 HTMLInputElementImpl* in = static_cast< HTMLInputElementImpl* >( element );
2883 switch( in->inputType()) {
2884 case HTMLInputElementImpl::SUBMIT:
2885 text = in->value().string();
2886 if( text.isEmpty())
2887 text = i18n( "Submit" );
2888 priority = 7;
2889 break;
2890 case HTMLInputElementImpl::IMAGE:
2891 text = in->altText().string();
2892 priority = 7;
2893 break;
2894 case HTMLInputElementImpl::BUTTON:
2895 text = in->value().string();
2896 priority = 5;
2897 break;
2898 case HTMLInputElementImpl::RESET:
2899 text = in->value().string();
2900 if( text.isEmpty())
2901 text = i18n( "Reset" );
2902 priority = 5;
2903 break;
2904 case HTMLInputElementImpl::HIDDEN:
2905 ignore = true;
2906 break;
2907 case HTMLInputElementImpl::CHECKBOX:
2908 case HTMLInputElementImpl::RADIO:
2909 text_after = true;
2910 priority = 5;
2911 break;
2912 case HTMLInputElementImpl::TEXT:
2913 case HTMLInputElementImpl::PASSWORD:
2914 case HTMLInputElementImpl::FILE:
2915 text_before = true;
2916 priority = 5;
2917 break;
2918 default:
2919 priority = 5;
2920 break;
2922 break;
2924 case ID_BUTTON:
2925 text = static_cast< HTMLElementImpl* >( element )->innerText().string().simplified();
2926 switch( static_cast< HTMLButtonElementImpl* >( element )->buttonType()) {
2927 case HTMLButtonElementImpl::SUBMIT:
2928 if( text.isEmpty())
2929 text = i18n( "Submit" );
2930 priority = 7;
2931 break;
2932 case HTMLButtonElementImpl::RESET:
2933 if( text.isEmpty())
2934 text = i18n( "Reset" );
2935 priority = 5;
2936 break;
2937 default:
2938 priority = 5;
2939 break;
2941 break;
2942 case ID_SELECT: // these don't have accesskey attribute, but quick access may be handy
2943 text_before = true;
2944 text_after = true;
2945 priority = 5;
2946 break;
2947 case ID_FRAME:
2948 ignore = true;
2949 break;
2950 default:
2951 ignore = !element->isFocusable();
2952 priority = 2;
2953 break;
2955 if( ignore )
2956 continue;
2957 if( text.isNull() && labels.contains( element ))
2958 text = labels[ element ];
2959 if( text.isNull() && text_before )
2960 text = getElementText( element, false );
2961 if( text.isNull() && text_after )
2962 text = getElementText( element, true );
2963 text = text.trimmed();
2964 // increase priority of items which have explicitly specified accesskeys in the config
2965 const QList< QPair< QString, QChar > > priorities
2966 = m_part->settings()->fallbackAccessKeysAssignments();
2967 for( QList< QPair< QString, QChar > >::ConstIterator it = priorities.begin();
2968 it != priorities.end();
2969 ++it ) {
2970 if( text == (*it).first )
2971 priority = 10;
2973 AccessKeyData tmp = { element, text, url, priority };
2974 data.append( tmp );
2978 QList< QChar > keys;
2979 for( char c = 'A'; c <= 'Z'; ++c )
2980 keys << c;
2981 for( char c = '0'; c <= '9'; ++c )
2982 keys << c;
2983 for( NodeImpl* n = m_part->xmlDocImpl();
2984 n != NULL;
2985 n = n->traverseNextNode()) {
2986 if( n->isElementNode()) {
2987 ElementImpl* en = static_cast< ElementImpl* >( n );
2988 DOMString s = en->getAttribute( ATTR_ACCESSKEY );
2989 if( s.length() == 1 ) {
2990 QChar c = s.string()[ 0 ].toUpper();
2991 keys.removeAll( c ); // remove manually assigned accesskeys
2996 QMap< ElementImpl*, QChar > ret;
2997 for( int priority = 10; priority >= 0; --priority ) {
2998 for( QLinkedList< AccessKeyData >::Iterator it = data.begin();
2999 it != data.end();
3001 if( (*it).priority != priority ) {
3002 ++it;
3003 continue;
3005 if( keys.isEmpty())
3006 break;
3007 QString text = (*it).text;
3008 QChar key;
3009 if( key.isNull() && !text.isEmpty()) {
3010 const QList< QPair< QString, QChar > > priorities
3011 = m_part->settings()->fallbackAccessKeysAssignments();
3012 for( QList< QPair< QString, QChar > >::ConstIterator it = priorities.begin();
3013 it != priorities.end();
3014 ++it )
3015 if( text == (*it).first && keys.contains( (*it).second )) {
3016 key = (*it).second;
3017 break;
3020 // try first to select the first character as the accesskey,
3021 // then first character of the following words,
3022 // and then simply the first free character
3023 if( key.isNull() && !text.isEmpty()) {
3024 const QStringList words = text.split( ' ' );
3025 for( QStringList::ConstIterator it = words.begin();
3026 it != words.end();
3027 ++it ) {
3028 if( keys.contains( (*it)[ 0 ].toUpper())) {
3029 key = (*it)[ 0 ].toUpper();
3030 break;
3034 if( key.isNull() && !text.isEmpty()) {
3035 for( int i = 0; i < text.length(); ++i ) {
3036 if( keys.contains( text[ i ].toUpper())) {
3037 key = text[ i ].toUpper();
3038 break;
3042 if( key.isNull())
3043 key = keys.front();
3044 ret[ (*it).element ] = key;
3045 keys.removeAll( key );
3046 QString url = (*it).url;
3047 it = data.erase( it );
3048 // assign the same accesskey also to other elements pointing to the same url
3049 if( !url.isEmpty() && !url.startsWith( "javascript:", Qt::CaseInsensitive )) {
3050 for( QLinkedList< AccessKeyData >::Iterator it2 = data.begin();
3051 it2 != data.end();
3053 if( (*it2).url == url ) {
3054 ret[ (*it2).element ] = key;
3055 if( it == it2 )
3056 ++it;
3057 it2 = data.erase( it2 );
3058 } else
3059 ++it2;
3064 return ret;
3067 void KHTMLView::setMediaType( const QString &medium )
3069 m_medium = medium;
3072 QString KHTMLView::mediaType() const
3074 return m_medium;
3077 bool KHTMLView::pagedMode() const
3079 return d->paged;
3082 void KHTMLView::setWidgetVisible(RenderWidget* w, bool vis)
3084 if (vis) {
3085 d->visibleWidgets.insert(w, w->widget());
3087 else
3088 d->visibleWidgets.remove(w);
3091 bool KHTMLView::needsFullRepaint() const
3093 return d->needsFullRepaint;
3096 namespace {
3097 class QPointerDeleter
3099 public:
3100 explicit QPointerDeleter(QObject* o) : obj(o) {}
3101 ~QPointerDeleter() { delete obj; }
3102 private:
3103 const QPointer<QObject> obj;
3107 void KHTMLView::print(bool quick)
3109 if(!m_part->xmlDocImpl()) return;
3110 khtml::RenderCanvas *root = static_cast<khtml::RenderCanvas *>(m_part->xmlDocImpl()->renderer());
3111 if(!root) return;
3113 QPointer<KHTMLPrintSettings> printSettings(new KHTMLPrintSettings); //XXX: doesn't save settings between prints like this
3114 const QPointerDeleter settingsDeleter(printSettings); //the printdialog takes ownership of the settings widget, thus this workaround to avoid double deletion
3115 QPrinter printer;
3116 QPointer<QPrintDialog> dialog = KdePrint::createPrintDialog(&printer, QList<QWidget*>() << printSettings, this);
3117 const QPointerDeleter dialogDeleter(dialog);
3119 QString docname = m_part->xmlDocImpl()->URL().prettyUrl();
3120 if ( !docname.isEmpty() )
3121 docname = KStringHandler::csqueeze(docname, 80);
3123 if(quick || (dialog->exec() && dialog)) { /*'this' and thus dialog might have been deleted while exec()!*/
3124 viewport()->setCursor( Qt::WaitCursor ); // only viewport(), no QApplication::, otherwise we get the busy cursor in kdeprint's dialogs
3125 // set up KPrinter
3126 printer.setFullPage(false);
3127 printer.setCreator(QString("KDE %1.%2.%3 HTML Library").arg(KDE_VERSION_MAJOR).arg(KDE_VERSION_MINOR).arg(KDE_VERSION_RELEASE));
3128 printer.setDocName(docname);
3130 QPainter *p = new QPainter;
3131 p->begin( &printer );
3132 khtml::setPrintPainter( p );
3134 m_part->xmlDocImpl()->setPaintDevice( &printer );
3135 QString oldMediaType = mediaType();
3136 setMediaType( "print" );
3137 // We ignore margin settings for html and body when printing
3138 // and use the default margins from the print-system
3139 // (In Qt 3.0.x the default margins are hardcoded in Qt)
3140 m_part->xmlDocImpl()->setPrintStyleSheet( printSettings->printFriendly() ?
3141 "* { background-image: none !important;"
3142 " background-color: white !important;"
3143 " color: black !important; }"
3144 "body { margin: 0px !important; }"
3145 "html { margin: 0px !important; }" :
3146 "body { margin: 0px !important; }"
3147 "html { margin: 0px !important; }"
3150 kDebug(6000) << "printing: physical page width = " << printer.width()
3151 << " height = " << printer.height() << endl;
3152 root->setStaticMode(true);
3153 root->setPagedMode(true);
3154 root->setWidth(printer.width());
3155 // root->setHeight(printer.height());
3156 root->setPageTop(0);
3157 root->setPageBottom(0);
3158 d->paged = true;
3160 m_part->xmlDocImpl()->styleSelector()->computeFontSizes(printer.logicalDpiY(), 100);
3161 m_part->xmlDocImpl()->updateStyleSelector();
3162 root->setPrintImages(printSettings->printImages());
3163 root->makePageBreakAvoidBlocks();
3165 root->setNeedsLayoutAndMinMaxRecalc();
3166 root->layout();
3168 // check sizes ask for action.. (scale or clip)
3170 bool printHeader = printSettings->printHeader();
3172 int headerHeight = 0;
3173 QFont headerFont("Sans Serif", 8);
3175 QString headerLeft = KGlobal::locale()->formatDate(QDate::currentDate(),KLocale::ShortDate);
3176 QString headerMid = docname;
3177 QString headerRight;
3179 if (printHeader)
3181 p->setFont(headerFont);
3182 headerHeight = (p->fontMetrics().lineSpacing() * 3) / 2;
3185 // ok. now print the pages.
3186 kDebug(6000) << "printing: html page width = " << root->docWidth()
3187 << " height = " << root->docHeight() << endl;
3188 kDebug(6000) << "printing: margins left = " << printer.pageRect().left() - printer.paperRect().left()
3189 << " top = " << printer.pageRect().top() - printer.paperRect().top() << endl;
3190 kDebug(6000) << "printing: paper width = " << printer.width()
3191 << " height = " << printer.height() << endl;
3192 // if the width is too large to fit on the paper we just scale
3193 // the whole thing.
3194 int pageWidth = printer.width();
3195 int pageHeight = printer.height();
3196 p->setClipRect(0,0, pageWidth, pageHeight);
3198 pageHeight -= headerHeight;
3200 bool scalePage = false;
3201 double scale = 0.0;
3202 #ifndef QT_NO_TRANSFORMATIONS
3203 if(root->docWidth() > printer.width()) {
3204 scalePage = true;
3205 scale = ((double) printer.width())/((double) root->docWidth());
3206 pageHeight = (int) (pageHeight/scale);
3207 pageWidth = (int) (pageWidth/scale);
3208 headerHeight = (int) (headerHeight/scale);
3210 #endif
3211 kDebug(6000) << "printing: scaled html width = " << pageWidth
3212 << " height = " << pageHeight << endl;
3214 root->setHeight(pageHeight);
3215 root->setPageBottom(pageHeight);
3216 root->setNeedsLayout(true);
3217 root->layoutIfNeeded();
3218 // m_part->slotDebugRenderTree();
3220 // Squeeze header to make it it on the page.
3221 if (printHeader)
3223 int available_width = printer.width() - 10 -
3224 2 * qMax(p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerLeft).width(),
3225 p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerRight).width());
3226 if (available_width < 150)
3227 available_width = 150;
3228 int mid_width;
3229 int squeeze = 120;
3230 do {
3231 headerMid = KStringHandler::csqueeze(docname, squeeze);
3232 mid_width = p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerMid).width();
3233 squeeze -= 10;
3234 } while (mid_width > available_width);
3237 int top = 0;
3238 int bottom = 0;
3239 int page = 1;
3240 while(top < root->docHeight()) {
3241 if(top > 0) printer.newPage();
3242 p->save();
3243 p->setClipRect(0, 0, pageWidth, headerHeight);
3244 if (printHeader)
3246 int dy = p->fontMetrics().lineSpacing();
3247 p->setPen(Qt::black);
3248 p->setFont(headerFont);
3250 headerRight = QString("#%1").arg(page);
3252 p->drawText(0, 0, printer.width(), dy, Qt::AlignLeft, headerLeft);
3253 p->drawText(0, 0, printer.width(), dy, Qt::AlignHCenter, headerMid);
3254 p->drawText(0, 0, printer.width(), dy, Qt::AlignRight, headerRight);
3257 #ifndef QT_NO_TRANSFORMATIONS
3258 if (scalePage)
3259 p->scale(scale, scale);
3260 #endif
3261 p->restore();
3262 p->translate(0, headerHeight-top);
3264 bottom = top+pageHeight;
3266 root->setPageTop(top);
3267 root->setPageBottom(bottom);
3268 root->setPageNumber(page);
3270 root->layer()->paint(p, QRect(0, top, pageWidth, pageHeight));
3271 kDebug(6000) << "printed: page " << page <<" bottom At = " << bottom;
3273 top = bottom;
3274 p->resetTransform();
3275 page++;
3278 p->end();
3279 delete p;
3281 // and now reset the layout to the usual one...
3282 root->setPagedMode(false);
3283 root->setStaticMode(false);
3284 d->paged = false;
3285 khtml::setPrintPainter( 0 );
3286 setMediaType( oldMediaType );
3287 m_part->xmlDocImpl()->setPaintDevice( this );
3288 m_part->xmlDocImpl()->styleSelector()->computeFontSizes(m_part->xmlDocImpl()->logicalDpiY(), m_part->fontScaleFactor());
3289 m_part->xmlDocImpl()->updateStyleSelector();
3290 viewport()->unsetCursor();
3294 void KHTMLView::slotPaletteChanged()
3296 if(!m_part->xmlDocImpl()) return;
3297 DOM::DocumentImpl *document = m_part->xmlDocImpl();
3298 if (!document->isHTMLDocument()) return;
3299 khtml::RenderCanvas *root = static_cast<khtml::RenderCanvas *>(document->renderer());
3300 if(!root) return;
3301 root->style()->resetPalette();
3302 NodeImpl *body = static_cast<HTMLDocumentImpl*>(document)->body();
3303 if(!body) return;
3304 body->setChanged(true);
3305 body->recalcStyle( NodeImpl::Force );
3308 void KHTMLView::paint(QPainter *p, const QRect &rc, int yOff, bool *more)
3310 if(!m_part->xmlDocImpl()) return;
3311 khtml::RenderCanvas *root = static_cast<khtml::RenderCanvas *>(m_part->xmlDocImpl()->renderer());
3312 if(!root) return;
3313 d->firstRepaintPending = false;
3315 QPaintDevice* opd = m_part->xmlDocImpl()->paintDevice();
3316 m_part->xmlDocImpl()->setPaintDevice(p->device());
3317 root->setPagedMode(true);
3318 root->setStaticMode(true);
3319 root->setWidth(rc.width());
3321 // save()
3322 QRegion creg = p->clipRegion();
3323 QTransform t = p->worldTransform();
3324 QRect w = p->window();
3325 QRect v = p->viewport();
3326 bool vte = p->viewTransformEnabled();
3327 bool wme = p->worldMatrixEnabled();
3329 p->setClipRect(rc);
3330 p->translate(rc.left(), rc.top());
3331 double scale = ((double) rc.width()/(double) root->docWidth());
3332 int height = (int) ((double) rc.height() / scale);
3333 #ifndef QT_NO_TRANSFORMATIONS
3334 p->scale(scale, scale);
3335 #endif
3336 root->setPageTop(yOff);
3337 root->setPageBottom(yOff+height);
3339 root->layer()->paint(p, QRect(0, yOff, root->docWidth(), height));
3340 if (more)
3341 *more = yOff + height < root->docHeight();
3343 // restore()
3344 p->setWorldTransform(t);
3345 p->setWindow(w);
3346 p->setViewport(v);
3347 p->setViewTransformEnabled( vte );
3348 p->setWorldMatrixEnabled( wme );
3349 if (!creg.isEmpty())
3350 p->setClipRegion( creg );
3351 else
3352 p->setClipRegion(QRegion(), Qt::NoClip);
3354 root->setPagedMode(false);
3355 root->setStaticMode(false);
3356 m_part->xmlDocImpl()->setPaintDevice( opd );
3359 void KHTMLView::render(QPainter* p, const QRect& r, const QPoint& off)
3361 d->firstRepaintPending = false;
3362 QRect clip(off.x()+r.x(), off.y()+r.y(),r.width(),r.height());
3363 if(!m_part || !m_part->xmlDocImpl() || !m_part->xmlDocImpl()->renderer()) {
3364 p->fillRect(clip, palette().brush(QPalette::Active, QPalette::Base));
3365 return;
3367 QPaintDevice* opd = m_part->xmlDocImpl()->paintDevice();
3368 m_part->xmlDocImpl()->setPaintDevice(p->device());
3370 // save()
3371 QRegion creg = p->clipRegion();
3372 QTransform t = p->worldTransform();
3373 QRect w = p->window();
3374 QRect v = p->viewport();
3375 bool vte = p->viewTransformEnabled();
3376 bool wme = p->worldMatrixEnabled();
3378 p->setClipRect(clip);
3379 QRect rect = r.translated(contentsX(),contentsY());
3380 p->translate(off.x()-contentsX(), off.y()-contentsY());
3382 m_part->xmlDocImpl()->renderer()->layer()->paint(p, rect);
3384 // restore()
3385 p->setWorldTransform(t);
3386 p->setWindow(w);
3387 p->setViewport(v);
3388 p->setViewTransformEnabled( vte );
3389 p->setWorldMatrixEnabled( wme );
3390 if (!creg.isEmpty())
3391 p->setClipRegion( creg );
3392 else
3393 p->setClipRegion(QRegion(), Qt::NoClip);
3395 m_part->xmlDocImpl()->setPaintDevice( opd );
3398 void KHTMLView::setHasStaticBackground(bool partial)
3400 // full static iframe is irreversible for now
3401 if (d->staticWidget == KHTMLViewPrivate::SBFull && m_kwp->isRedirected())
3402 return;
3404 d->staticWidget = partial ?
3405 KHTMLViewPrivate::SBPartial : KHTMLViewPrivate::SBFull;
3408 void KHTMLView::setHasNormalBackground()
3410 // full static iframe is irreversible for now
3411 if (d->staticWidget == KHTMLViewPrivate::SBFull && m_kwp->isRedirected())
3412 return;
3414 d->staticWidget = KHTMLViewPrivate::SBNone;
3417 void KHTMLView::addStaticObject(bool fixed)
3419 if (fixed)
3420 d->fixedObjectsCount++;
3421 else
3422 d->staticObjectsCount++;
3424 setHasStaticBackground( true /*partial*/ );
3427 void KHTMLView::removeStaticObject(bool fixed)
3429 if (fixed)
3430 d->fixedObjectsCount--;
3431 else
3432 d->staticObjectsCount--;
3434 assert( d->fixedObjectsCount >= 0 && d->staticObjectsCount >= 0 );
3436 if (!d->staticObjectsCount && !d->fixedObjectsCount)
3437 setHasNormalBackground();
3438 else
3439 setHasStaticBackground( true /*partial*/ );
3442 void KHTMLView::setVerticalScrollBarPolicy( Qt::ScrollBarPolicy policy )
3444 #ifndef KHTML_NO_SCROLLBARS
3445 d->vpolicy = policy;
3446 QScrollArea::setVerticalScrollBarPolicy(policy);
3447 #else
3448 Q_UNUSED( policy );
3449 #endif
3452 void KHTMLView::setHorizontalScrollBarPolicy( Qt::ScrollBarPolicy policy )
3454 #ifndef KHTML_NO_SCROLLBARS
3455 d->hpolicy = policy;
3456 QScrollArea::setHorizontalScrollBarPolicy(policy);
3457 #else
3458 Q_UNUSED( policy );
3459 #endif
3462 void KHTMLView::restoreScrollBar()
3464 int ow = visibleWidth();
3465 QScrollArea::setVerticalScrollBarPolicy(d->vpolicy);
3466 if (visibleWidth() != ow)
3467 layout();
3468 d->prevScrollbarVisible = verticalScrollBar()->isVisible();
3471 QStringList KHTMLView::formCompletionItems(const QString &name) const
3473 if (!m_part->settings()->isFormCompletionEnabled())
3474 return QStringList();
3475 if (!d->formCompletions)
3476 d->formCompletions = new KConfig(KStandardDirs::locateLocal("data", "khtml/formcompletions"));
3477 return d->formCompletions->group("").readEntry(name, QStringList());
3480 void KHTMLView::clearCompletionHistory(const QString& name)
3482 if (!d->formCompletions)
3484 d->formCompletions = new KConfig(KStandardDirs::locateLocal("data", "khtml/formcompletions"));
3486 d->formCompletions->group("").writeEntry(name, "");
3487 d->formCompletions->sync();
3490 void KHTMLView::addFormCompletionItem(const QString &name, const QString &value)
3492 if (!m_part->settings()->isFormCompletionEnabled())
3493 return;
3494 // don't store values that are all numbers or just numbers with
3495 // dashes or spaces as those are likely credit card numbers or
3496 // something similar
3497 bool cc_number(true);
3498 for ( int i = 0; i < value.length(); ++i)
3500 QChar c(value[i]);
3501 if (!c.isNumber() && c != '-' && !c.isSpace())
3503 cc_number = false;
3504 break;
3507 if (cc_number)
3508 return;
3509 QStringList items = formCompletionItems(name);
3510 if (!items.contains(value))
3511 items.prepend(value);
3512 while ((int)items.count() > m_part->settings()->maxFormCompletionItems())
3513 items.erase(items.isEmpty() ? items.end() : --items.end());
3514 d->formCompletions->group("").writeEntry(name, items);
3517 void KHTMLView::addNonPasswordStorableSite(const QString& host)
3519 if (!d->formCompletions) {
3520 d->formCompletions = new KConfig(KStandardDirs::locateLocal("data", "khtml/formcompletions"));
3523 KConfigGroup cg( d->formCompletions, "NonPasswordStorableSites");
3524 QStringList sites = cg.readEntry("Sites", QStringList());
3525 sites.append(host);
3526 cg.writeEntry("Sites", sites);
3527 cg.sync();
3530 bool KHTMLView::nonPasswordStorableSite(const QString& host) const
3532 if (!d->formCompletions) {
3533 d->formCompletions = new KConfig(KStandardDirs::locateLocal("data", "khtml/formcompletions"));
3535 QStringList sites = d->formCompletions->group( "NonPasswordStorableSites" ).readEntry("Sites", QStringList());
3536 return (sites.indexOf(host) != -1);
3539 // returns true if event should be swallowed
3540 bool KHTMLView::dispatchMouseEvent(int eventId, DOM::NodeImpl *targetNode,
3541 DOM::NodeImpl *targetNodeNonShared, bool cancelable,
3542 int detail,QMouseEvent *_mouse, bool setUnder,
3543 int mouseEventType, int orient)
3545 // if the target node is a text node, dispatch on the parent node - rdar://4196646 (and #76948)
3546 if (targetNode && targetNode->isTextNode())
3547 targetNode = targetNode->parentNode();
3549 if (d->underMouse)
3550 d->underMouse->deref();
3551 d->underMouse = targetNode;
3552 if (d->underMouse)
3553 d->underMouse->ref();
3555 if (d->underMouseNonShared)
3556 d->underMouseNonShared->deref();
3557 d->underMouseNonShared = targetNodeNonShared;
3558 if (d->underMouseNonShared)
3559 d->underMouseNonShared->ref();
3561 bool isWheelEvent = (mouseEventType == DOM::NodeImpl::MouseWheel);
3563 int exceptioncode = 0;
3564 int pageX = _mouse->x();
3565 int pageY = _mouse->y();
3566 revertTransforms(pageX, pageY);
3567 int clientX = pageX - contentsX();
3568 int clientY = pageY - contentsY();
3569 int screenX = _mouse->globalX();
3570 int screenY = _mouse->globalY();
3571 int button = -1;
3572 switch (_mouse->button()) {
3573 case Qt::LeftButton:
3574 button = 0;
3575 break;
3576 case Qt::MidButton:
3577 button = 1;
3578 break;
3579 case Qt::RightButton:
3580 button = 2;
3581 break;
3582 default:
3583 break;
3585 if (d->accessKeysEnabled && d->accessKeysPreActivate && button!=-1)
3586 d->accessKeysPreActivate=false;
3588 bool ctrlKey = (_mouse->modifiers() & Qt::ControlModifier);
3589 bool altKey = (_mouse->modifiers() & Qt::AltModifier);
3590 bool shiftKey = (_mouse->modifiers() & Qt::ShiftModifier);
3591 bool metaKey = (_mouse->modifiers() & Qt::MetaModifier);
3593 // mouseout/mouseover
3594 if (setUnder && d->oldUnderMouse != targetNode) {
3595 if (d->oldUnderMouse && d->oldUnderMouse->document() != m_part->xmlDocImpl()) {
3596 d->oldUnderMouse->deref();
3597 d->oldUnderMouse = 0;
3599 // send mouseout event to the old node
3600 if (d->oldUnderMouse) {
3601 // send mouseout event to the old node
3602 MouseEventImpl *me = new MouseEventImpl(EventImpl::MOUSEOUT_EVENT,
3603 true,true,m_part->xmlDocImpl()->defaultView(),
3604 0,screenX,screenY,clientX,clientY,pageX, pageY,
3605 ctrlKey,altKey,shiftKey,metaKey,
3606 button,targetNode);
3607 me->ref();
3608 d->oldUnderMouse->dispatchEvent(me,exceptioncode,true);
3609 me->deref();
3611 // send mouseover event to the new node
3612 if (targetNode) {
3613 MouseEventImpl *me = new MouseEventImpl(EventImpl::MOUSEOVER_EVENT,
3614 true,true,m_part->xmlDocImpl()->defaultView(),
3615 0,screenX,screenY,clientX,clientY,pageX, pageY,
3616 ctrlKey,altKey,shiftKey,metaKey,
3617 button,d->oldUnderMouse);
3619 me->ref();
3620 targetNode->dispatchEvent(me,exceptioncode,true);
3621 me->deref();
3623 if (d->oldUnderMouse)
3624 d->oldUnderMouse->deref();
3625 d->oldUnderMouse = targetNode;
3626 if (d->oldUnderMouse)
3627 d->oldUnderMouse->ref();
3630 bool swallowEvent = false;
3632 if (targetNode) {
3633 // if the target node is a disabled widget, we don't want any full-blown mouse events
3634 if (targetNode->isGenericFormElement()
3635 && static_cast<HTMLGenericFormElementImpl*>(targetNode)->disabled())
3636 return true;
3638 // send the actual event
3639 bool dblclick = ( eventId == EventImpl::CLICK_EVENT &&
3640 _mouse->type() == QEvent::MouseButtonDblClick );
3641 MouseEventImpl *me = new MouseEventImpl(static_cast<EventImpl::EventId>(eventId),
3642 true,cancelable,m_part->xmlDocImpl()->defaultView(),
3643 detail,screenX,screenY,clientX,clientY,pageX, pageY,
3644 ctrlKey,altKey,shiftKey,metaKey,
3645 button,0, isWheelEvent ? 0 : _mouse, dblclick,
3646 isWheelEvent ? static_cast<MouseEventImpl::Orientation>(orient) : MouseEventImpl::ONone );
3647 me->ref();
3648 if ( !d->m_mouseEventsTarget && RenderLayer::gScrollBar && eventId == EventImpl::MOUSEDOWN_EVENT )
3649 // button is pressed inside a layer scrollbar, so make it the target for future mousemove events until released
3650 d->m_mouseEventsTarget = RenderLayer::gScrollBar;
3651 if ( d->m_mouseEventsTarget && qobject_cast<QScrollBar*>(d->m_mouseEventsTarget) &&
3652 dynamic_cast<KHTMLWidget*>(static_cast<QWidget*>(d->m_mouseEventsTarget)) ) {
3653 // we have a sticky mouse event target and it is a layer's scrollbar. Forward events manually.
3654 // ### should use the dom
3655 KHTMLWidget*w = dynamic_cast<KHTMLWidget*>(static_cast<QWidget*>(d->m_mouseEventsTarget));
3656 QPoint p = w->m_kwp->absolutePos();
3657 QMouseEvent fw(_mouse->type(), QPoint(pageX, pageY)-p, _mouse->button(), _mouse->buttons(), _mouse->modifiers());
3658 static_cast<RenderWidget::EventPropagator *>(static_cast<QWidget*>(d->m_mouseEventsTarget))->sendEvent(&fw);
3659 if (_mouse->type() == QMouseEvent::MouseButtonPress && _mouse->button() == Qt::RightButton) {
3660 QContextMenuEvent cme(QContextMenuEvent::Mouse, p);
3661 static_cast<RenderWidget::EventPropagator *>(static_cast<QWidget*>(d->m_mouseEventsTarget))->sendEvent(&cme);
3662 d->m_mouseEventsTarget = 0;
3664 swallowEvent = true;
3665 } else {
3666 targetNode->dispatchEvent(me,exceptioncode,true);
3667 bool defaultHandled = me->defaultHandled();
3668 if (defaultHandled || me->defaultPrevented())
3669 swallowEvent = true;
3671 me->deref();
3673 if (eventId == EventImpl::MOUSEDOWN_EVENT) {
3674 // Focus should be shifted on mouse down, not on a click. -dwh
3675 // Blur current focus node when a link/button is clicked; this
3676 // is expected by some sites that rely on onChange handlers running
3677 // from form fields before the button click is processed.
3678 DOM::NodeImpl* nodeImpl = targetNode;
3679 for ( ; nodeImpl && !nodeImpl->isFocusable(); nodeImpl = nodeImpl->parentNode())
3681 if (nodeImpl && nodeImpl->isMouseFocusable())
3682 m_part->xmlDocImpl()->setFocusNode(nodeImpl);
3683 else if (!nodeImpl || !nodeImpl->focused())
3684 m_part->xmlDocImpl()->setFocusNode(0);
3688 return swallowEvent;
3691 void KHTMLView::setIgnoreWheelEvents( bool e )
3693 d->ignoreWheelEvents = e;
3696 #ifndef QT_NO_WHEELEVENT
3698 void KHTMLView::wheelEvent(QWheelEvent* e)
3700 // check if we should reset the state of the indicator describing if
3701 // we are currently scrolling the view as a result of wheel events
3702 if (d->scrollingFromWheel != QPoint(-1,-1) && d->scrollingFromWheel != QCursor::pos())
3703 d->scrollingFromWheel = d->scrollingFromWheelTimerId ? QCursor::pos() : QPoint(-1,-1);
3705 if (d->accessKeysEnabled && d->accessKeysPreActivate) d->accessKeysPreActivate=false;
3707 if ( ( e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier )
3709 emit zoomView( - e->delta() );
3710 e->accept();
3712 else if (d->firstLayoutPending)
3714 e->accept();
3716 else if( !m_kwp->isRedirected() &&
3717 ( (e->orientation() == Qt::Vertical &&
3718 ((d->ignoreWheelEvents && !verticalScrollBar()->isVisible())
3719 || (e->delta() > 0 && contentsY() <= 0)
3720 || (e->delta() < 0 && contentsY() >= contentsHeight() - visibleHeight())))
3722 (e->orientation() == Qt::Horizontal &&
3723 ((d->ignoreWheelEvents && !horizontalScrollBar()->isVisible())
3724 || (e->delta() > 0 && contentsX() <=0)
3725 || (e->delta() < 0 && contentsX() >= contentsWidth() - visibleWidth()))))
3726 && m_part->parentPart())
3728 if ( m_part->parentPart()->view() )
3729 m_part->parentPart()->view()->wheelEvent( e );
3730 e->ignore();
3732 else
3734 int xm = e->x();
3735 int ym = e->y();
3736 revertTransforms(xm, ym);
3738 DOM::NodeImpl::MouseEvent mev( e->buttons(), DOM::NodeImpl::MouseWheel );
3739 m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev );
3741 MouseEventImpl::Orientation o = MouseEventImpl::OVertical;
3742 if (e->orientation() == Qt::Horizontal)
3743 o = MouseEventImpl::OHorizontal;
3745 QMouseEvent _mouse(QEvent::MouseMove, QPoint(xm,ym), Qt::NoButton, e->buttons(), e->modifiers());
3746 bool swallow = dispatchMouseEvent(EventImpl::KHTML_MOUSEWHEEL_EVENT,mev.innerNode.handle(),mev.innerNonSharedNode.handle(),
3747 true,-e->delta()/40,&_mouse,true,DOM::NodeImpl::MouseWheel,o);
3749 if (swallow)
3750 return;
3752 d->scrollBarMoved = true;
3753 d->scrollingFromWheel = QCursor::pos();
3754 if (d->smoothScrollMode != SSMDisabled)
3755 d->shouldSmoothScroll = true;
3756 if (d->scrollingFromWheelTimerId)
3757 killTimer(d->scrollingFromWheelTimerId);
3758 d->scrollingFromWheelTimerId = startTimer(400);
3760 if (m_part->parentPart()) {
3761 // don't propagate if we are a sub-frame and our scrollbars are already at end of range
3762 bool h = (static_cast<QWheelEvent*>(e)->orientation() == Qt::Horizontal);
3763 bool d = (static_cast<QWheelEvent*>(e)->delta() < 0);
3764 QScrollBar* hsb = horizontalScrollBar();
3765 QScrollBar* vsb = verticalScrollBar();
3766 if ( (h && ((d && hsb->value() == hsb->maximum()) || (!d && hsb->value() == hsb->minimum()))) ||
3767 (!h && ((d && vsb->value() == vsb->maximum()) || (!d && vsb->value() == vsb->minimum()))) ) {
3768 e->accept();
3769 return;
3772 QScrollArea::wheelEvent( e );
3776 #endif
3778 void KHTMLView::dragEnterEvent( QDragEnterEvent* ev )
3780 // Still overridden for BC reasons only...
3781 QScrollArea::dragEnterEvent( ev );
3784 void KHTMLView::dropEvent( QDropEvent *ev )
3786 // Still overridden for BC reasons only...
3787 QScrollArea::dropEvent( ev );
3790 void KHTMLView::focusInEvent( QFocusEvent *e )
3792 DOM::NodeImpl* fn = m_part->xmlDocImpl() ? m_part->xmlDocImpl()->focusNode() : 0;
3793 #ifndef KHTML_NO_TYPE_AHEAD_FIND
3794 if (!fn || m_part->isCaretMode())
3795 m_part->enableFindAheadActions( true );
3796 #endif
3797 if (fn && fn->renderer() && fn->renderer()->isWidget() &&
3798 (e->reason() != Qt::MouseFocusReason) &&
3799 static_cast<khtml::RenderWidget*>(fn->renderer())->widget())
3800 static_cast<khtml::RenderWidget*>(fn->renderer())->widget()->setFocus();
3801 m_part->setSelectionVisible();
3802 QScrollArea::focusInEvent( e );
3805 void KHTMLView::focusOutEvent( QFocusEvent *e )
3807 if (m_part) {
3808 m_part->stopAutoScroll();
3809 m_part->setSelectionVisible(false);
3812 #ifndef KHTML_NO_TYPE_AHEAD_FIND
3813 if(d->typeAheadActivated)
3815 findTimeout();
3817 if (m_part)
3818 m_part->enableFindAheadActions( false );
3819 #endif // KHTML_NO_TYPE_AHEAD_FIND
3821 if ( d->cursorIconWidget )
3822 d->cursorIconWidget->hide();
3824 QScrollArea::focusOutEvent( e );
3827 void KHTMLView::scrollContentsBy( int dx, int dy )
3829 if (!dx && !dy) return;
3831 if ( !d->firstLayoutPending && !d->complete && m_part->xmlDocImpl() &&
3832 d->layoutSchedulingEnabled) {
3833 // contents scroll while we are not complete: we need to check our layout *now*
3834 khtml::RenderCanvas* root = static_cast<khtml::RenderCanvas *>( m_part->xmlDocImpl()->renderer() );
3835 if (root && root->needsLayout()) {
3836 unscheduleRelayout();
3837 layout();
3841 if ( d->shouldSmoothScroll && d->smoothScrollMode != SSMDisabled && m_part->xmlDocImpl() &&
3842 m_part->xmlDocImpl()->renderer()) {
3844 bool doSmoothScroll = (!d->staticWidget || d->smoothScrollMode == SSMEnabled);
3846 int numStaticPixels = 0;
3847 QRegion r = static_cast<RenderCanvas*>(m_part->xmlDocImpl()->renderer())->staticRegion();
3849 // only do smooth scrolling if static region is relatively small
3850 if (!doSmoothScroll && d->staticWidget == KHTMLViewPrivate::SBPartial && r.rects().size() <= 10) {
3851 foreach(QRect rr, r.rects())
3852 numStaticPixels += rr.width()*rr.height();
3853 if ((numStaticPixels < sSmoothScrollMinStaticPixels) || (numStaticPixels*8 < visibleWidth()*visibleHeight()))
3854 doSmoothScroll = true;
3856 if (doSmoothScroll) {
3857 setupSmoothScrolling(dx, dy);
3858 return;
3862 if (!d->scrollingSelf) {
3863 d->scrollBarMoved = true;
3864 d->contentsMoving = true;
3865 // ensure quick reset of contentsMoving flag
3866 scheduleRepaint(0, 0, 0, 0);
3869 if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->documentElement()) {
3870 // ### FIXME: there is something wrong with this event.
3871 // With a capturing listener on document and window, window's should fire first, then document's.
3872 // Also, this doesn't work: <body onload="document.onscroll=function() {alert('ok')}"><div style=height:2000>
3873 m_part->xmlDocImpl()->documentElement()->dispatchWindowEvent(EventImpl::SCROLL_EVENT, false, false);
3876 if (QApplication::isRightToLeft())
3877 dx = -dx;
3879 if (!d->smoothScrolling) {
3880 d->updateContentsXY();
3881 } else {
3882 d->contentsX -= dx;
3883 d->contentsY -= dy;
3885 if (widget()->pos() != QPoint(0,0)) {
3886 kDebug(6000) << "Static widget wasn't positioned at (0,0). This should NOT happen. Please report this event to developers.";
3887 kDebug(6000) << kBacktrace();
3888 widget()->move(0,0);
3891 QWidget *w = widget();
3892 QPoint off;
3893 if (m_kwp->isRedirected()) {
3894 // This is a redirected sub frame. Translate to root view context
3895 KHTMLView* v = m_kwp->rootViewPos( off );
3896 if (v)
3897 w = v->widget();
3898 off = viewport()->mapTo(this, off);
3901 if ( d->staticWidget ) {
3903 // now remove from view the external widgets that must have completely
3904 // disappeared after dx/dy scroll delta is effective
3905 if (!d->visibleWidgets.isEmpty())
3906 checkExternalWidgetsPosition();
3908 if ( d->staticWidget == KHTMLViewPrivate::SBPartial
3909 && m_part->xmlDocImpl() && m_part->xmlDocImpl()->renderer() ) {
3910 // static objects might be selectively repainted, like stones in flowing water
3911 QRegion r = static_cast<RenderCanvas*>(m_part->xmlDocImpl()->renderer())->staticRegion();
3912 r.translate( -contentsX(), -contentsY());
3913 QVector<QRect> ar = r.rects();
3915 for (int i = 0; i < ar.size() ; ++i) {
3916 widget()->update( ar[i] );
3918 r = QRegion(QRect(0, 0, visibleWidth(), visibleHeight())) - r;
3919 ar = r.rects();
3920 for (int i = 0; i < ar.size() ; ++i) {
3921 w->scroll( dx, dy, ar[i].translated(off) );
3923 d->scrollExternalWidgets(dx, dy);
3924 } else {
3925 // we can't avoid a full update
3926 widget()->update();
3928 return;
3931 if (m_kwp->isRedirected()) {
3932 const QRect rect(off.x(), off.y(), visibleWidth() * d->zoomLevel / 100, visibleHeight() * d->zoomLevel / 100);
3933 w->scroll(dx, dy, rect);
3934 if (d->zoomLevel != 100) {
3935 w->update(rect); // without this update we are getting bad rendering when an iframe is zoomed in
3937 } else {
3938 widget()->scroll(dx, dy, widget()->rect() & viewport()->rect());
3941 d->scrollExternalWidgets(dx, dy);
3944 void KHTMLView::setupSmoothScrolling(int dx, int dy)
3946 // full scroll is remaining scroll plus new scroll
3947 d->dx = d->dx + dx;
3948 d->dy = d->dy + dy;
3950 if (d->dx == 0 && d->dy == 0) return;
3952 int steps = sSmoothScrollTime/sSmoothScrollTick;
3954 // average step size (stored in 1/16 px/step)
3955 d->ddx = (d->dx*16)/(steps+1);
3956 d->ddy = (d->dy*16)/(steps+1);
3958 if (abs(d->ddx) < 64 && abs(d->ddy) < 64) {
3959 // Don't move slower than average 4px/step in minimum one direction
3960 if (d->ddx > 0) d->ddx = qMax(d->ddx, 64);
3961 if (d->ddy > 0) d->ddy = qMax(d->ddy, 64);
3962 if (d->ddx < 0) d->ddx = qMin(d->ddx, -64);
3963 if (d->ddy < 0) d->ddy = qMin(d->ddy, -64);
3964 // This means fewer than normal steps
3965 steps = qMax(d->ddx ? (d->dx*16)/d->ddx : 0, d->ddy ? (d->dy*16)/d->ddy : 0);
3966 if (steps < 1) steps = 1;
3967 d->ddx = (d->dx*16)/(steps+1);
3968 d->ddy = (d->dy*16)/(steps+1);
3971 // step size starts at double average speed and ends at 0
3972 d->ddx *= 2;
3973 d->ddy *= 2;
3975 // deacceleration speed
3976 d->dddx = (d->ddx+1)/steps;
3977 d->dddy = (d->ddy+1)/steps;
3979 if (!d->smoothScrolling) {
3980 d->startScrolling();
3981 scrollTick();
3983 d->smoothScrollStopwatch.start();
3986 void KHTMLView::scrollTick() {
3987 if (d->dx == 0 && d->dy == 0) {
3988 d->stopScrolling();
3989 return;
3992 // step size + remaining partial step
3993 int tddx = d->ddx + d->rdx;
3994 int tddy = d->ddy + d->rdy;
3996 // don't go under 1px/step
3997 if (tddx > 0 && tddx < 16) tddx = 16;
3998 if (tddy > 0 && tddy < 16) tddy = 16;
3999 if (tddx < 0 && tddx > -16) tddx = -16;
4000 if (tddy < 0 && tddy > -16) tddy = -16;
4002 // full pixel steps to scroll in this step
4003 int ddx = tddx / 16;
4004 int ddy = tddy / 16;
4005 // remaining partial step (this is especially needed for 1.x sized steps)
4006 d->rdx = tddx % 16;
4007 d->rdy = tddy % 16;
4009 // limit step to requested scrolling distance
4010 if (abs(ddx) > abs(d->dx)) ddx = d->dx;
4011 if (abs(ddy) > abs(d->dy)) ddy = d->dy;
4013 // Don't stop if deaccelerated too fast
4014 if (!ddx) ddx = d->dx;
4015 if (!ddy) ddy = d->dy;
4017 // update remaining scroll
4018 d->dx -= ddx;
4019 d->dy -= ddy;
4021 d->shouldSmoothScroll = false;
4022 scrollContentsBy(ddx, ddy);
4024 // only consider decelerating if we aren't too far behind schedule
4025 if (d->smoothScrollStopwatch.elapsed() < 2*sSmoothScrollTick) {
4026 // update scrolling speed
4027 int dddx = d->dddx;
4028 int dddy = d->dddy;
4029 // don't change direction
4030 if (abs(dddx) > abs(d->ddx)) dddx = d->ddx;
4031 if (abs(dddy) > abs(d->ddy)) dddy = d->ddy;
4033 d->ddx -= dddx;
4034 d->ddy -= dddy;
4036 d->smoothScrollStopwatch.start();
4040 void KHTMLView::addChild(QWidget * child, int x, int y)
4042 if (!child)
4043 return;
4045 if (child->parent() != widget())
4046 child->setParent( widget() );
4048 // ### handle pseudo-zooming of non-redirected widgets (e.g. just resize'em)
4050 child->move(x-contentsX(), y-contentsY());
4053 void KHTMLView::timerEvent ( QTimerEvent *e )
4055 // kDebug() << "timer event " << e->timerId();
4056 if ( e->timerId() == d->scrollTimerId ) {
4057 if( d->scrollSuspended )
4058 return;
4059 switch (d->scrollDirection) {
4060 case KHTMLViewPrivate::ScrollDown:
4061 if (contentsY() + visibleHeight () >= contentsHeight())
4062 d->newScrollTimer(this, 0);
4063 else
4064 verticalScrollBar()->setValue( verticalScrollBar()->value() +d->scrollBy );
4065 break;
4066 case KHTMLViewPrivate::ScrollUp:
4067 if (contentsY() <= 0)
4068 d->newScrollTimer(this, 0);
4069 else
4070 verticalScrollBar()->setValue( verticalScrollBar()->value() -d->scrollBy );
4071 break;
4072 case KHTMLViewPrivate::ScrollRight:
4073 if (contentsX() + visibleWidth () >= contentsWidth())
4074 d->newScrollTimer(this, 0);
4075 else
4076 horizontalScrollBar()->setValue( horizontalScrollBar()->value() +d->scrollBy );
4077 break;
4078 case KHTMLViewPrivate::ScrollLeft:
4079 if (contentsX() <= 0)
4080 d->newScrollTimer(this, 0);
4081 else
4082 horizontalScrollBar()->setValue( horizontalScrollBar()->value() -d->scrollBy );
4083 break;
4085 return;
4087 else if ( e->timerId() == d->scrollingFromWheelTimerId ) {
4088 killTimer( d->scrollingFromWheelTimerId );
4089 d->scrollingFromWheelTimerId = 0;
4090 } else if ( e->timerId() == d->layoutTimerId ) {
4091 if (d->firstLayoutPending && d->layoutAttemptCounter < 4
4092 && (!m_part->xmlDocImpl() || !m_part->xmlDocImpl()->readyForLayout())) {
4093 d->layoutAttemptCounter++;
4094 killTimer(d->layoutTimerId);
4095 d->layoutTimerId = 0;
4096 scheduleRelayout();
4097 return;
4099 layout();
4100 d->scheduledLayoutCounter++;
4101 if (d->firstLayoutPending) {
4102 d->firstLayoutPending = false;
4103 verticalScrollBar()->setEnabled( true );
4104 horizontalScrollBar()->setEnabled( true );
4108 d->contentsMoving = false;
4109 if( m_part->xmlDocImpl() ) {
4110 DOM::DocumentImpl *document = m_part->xmlDocImpl();
4111 khtml::RenderCanvas* root = static_cast<khtml::RenderCanvas *>(document->renderer());
4113 if ( root && root->needsLayout() ) {
4114 if (d->repaintTimerId)
4115 killTimer(d->repaintTimerId);
4116 d->repaintTimerId = 0;
4117 scheduleRelayout();
4118 return;
4122 if (d->repaintTimerId)
4123 killTimer(d->repaintTimerId);
4124 d->repaintTimerId = 0;
4126 QRect updateRegion;
4127 const QVector<QRect> rects = d->updateRegion.rects();
4129 d->updateRegion = QRegion();
4131 if ( rects.size() )
4132 updateRegion = rects[0];
4134 for ( int i = 1; i < rects.size(); ++i ) {
4135 QRect newRegion = updateRegion.unite(rects[i]);
4136 if (2*newRegion.height() > 3*updateRegion.height() )
4138 repaintContents( updateRegion );
4139 updateRegion = rects[i];
4141 else
4142 updateRegion = newRegion;
4145 if ( !updateRegion.isNull() )
4146 repaintContents( updateRegion );
4148 // As widgets can only be accurately positioned during painting, every layout might
4149 // dissociate a widget from its RenderWidget. E.g: if a RenderWidget was visible before layout, but the layout
4150 // pushed it out of the viewport, it will not be repainted, and consequently it's associated widget won't be repositioned.
4151 // Thus we need to check each supposedly 'visible' widget at the end of layout, and remove it in case it's no more in sight.
4153 if (d->dirtyLayout && !d->visibleWidgets.isEmpty())
4154 checkExternalWidgetsPosition();
4156 d->dirtyLayout = false;
4158 emit repaintAccessKeys();
4159 if (d->emitCompletedAfterRepaint) {
4160 bool full = d->emitCompletedAfterRepaint == KHTMLViewPrivate::CSFull;
4161 d->emitCompletedAfterRepaint = KHTMLViewPrivate::CSNone;
4162 if ( full )
4163 emit m_part->completed();
4164 else
4165 emit m_part->completed(true);
4169 void KHTMLView::checkExternalWidgetsPosition()
4171 QWidget* w;
4172 QRect visibleRect(contentsX(), contentsY(), visibleWidth(), visibleHeight());
4173 QList<RenderWidget*> toRemove;
4174 QHashIterator<void*, QWidget*> it(d->visibleWidgets);
4175 while (it.hasNext()) {
4176 int xp = 0, yp = 0;
4177 it.next();
4178 RenderWidget* rw = static_cast<RenderWidget*>( it.key() );
4179 if (!rw->absolutePosition(xp, yp) ||
4180 !visibleRect.intersects(QRect(xp, yp, it.value()->width(), it.value()->height())))
4181 toRemove.append(rw);
4183 foreach (RenderWidget* r, toRemove)
4184 if ( (w = d->visibleWidgets.take(r) ) )
4185 w->move( 0, -500000);
4188 void KHTMLView::scheduleRelayout(khtml::RenderObject * /*clippedObj*/)
4190 if (!d->layoutSchedulingEnabled || d->layoutTimerId)
4191 return;
4193 int time = 0;
4194 if (d->firstLayoutPending) {
4195 // Any repaint happening while we have no content blanks the viewport ("white flash").
4196 // Hence the need to delay the first layout as much as we can.
4197 // Only if the document gets stuck for too long in incomplete state will we allow the blanking.
4198 time = d->layoutAttemptCounter ?
4199 sLayoutAttemptDelay + sLayoutAttemptIncrement*d->layoutAttemptCounter : sFirstLayoutDelay;
4200 } else if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->parsing()) {
4201 // Delay between successive layouts in parsing mode.
4202 // Increment reflects the decaying importance of visual feedback over time.
4203 time = qMin(2000, sParsingLayoutsInterval + d->scheduledLayoutCounter*sParsingLayoutsIncrement);
4205 d->layoutTimerId = startTimer( time );
4208 void KHTMLView::unscheduleRelayout()
4210 if (!d->layoutTimerId)
4211 return;
4213 killTimer(d->layoutTimerId);
4214 d->layoutTimerId = 0;
4217 void KHTMLView::unscheduleRepaint()
4219 if (!d->repaintTimerId)
4220 return;
4222 killTimer(d->repaintTimerId);
4223 d->repaintTimerId = 0;
4226 void KHTMLView::scheduleRepaint(int x, int y, int w, int h, bool asap)
4228 bool parsing = !m_part->xmlDocImpl() || m_part->xmlDocImpl()->parsing();
4230 // kDebug() << "parsing " << parsing;
4231 // kDebug() << "complete " << d->complete;
4233 int time = parsing && !d->firstLayoutPending ? 150 : (!asap ? ( !d->complete ? 80 : 20 ) : 0);
4235 #ifdef DEBUG_FLICKER
4236 QPainter p;
4237 p.begin( viewport() );
4239 int vx, vy;
4240 contentsToViewport( x, y, vx, vy );
4241 p.fillRect( vx, vy, w, h, Qt::red );
4242 p.end();
4243 #endif
4245 d->updateRegion = d->updateRegion.unite(QRect(x,y,w,h));
4247 if (asap && !parsing)
4248 unscheduleRepaint();
4250 if ( !d->repaintTimerId )
4251 d->repaintTimerId = startTimer( time );
4253 // kDebug() << "starting timer " << time;
4256 void KHTMLView::complete( bool pendingAction )
4258 // kDebug() << "KHTMLView::complete()";
4260 d->complete = true;
4262 // is there a relayout pending?
4263 if (d->layoutTimerId)
4265 // kDebug() << "requesting relayout now";
4266 // do it now
4267 killTimer(d->layoutTimerId);
4268 d->layoutTimerId = startTimer( 0 );
4269 d->emitCompletedAfterRepaint = pendingAction ?
4270 KHTMLViewPrivate::CSActionPending : KHTMLViewPrivate::CSFull;
4273 // is there a repaint pending?
4274 if (d->repaintTimerId)
4276 // kDebug() << "requesting repaint now";
4277 // do it now
4278 killTimer(d->repaintTimerId);
4279 d->repaintTimerId = startTimer( 0 );
4280 d->emitCompletedAfterRepaint = pendingAction ?
4281 KHTMLViewPrivate::CSActionPending : KHTMLViewPrivate::CSFull;
4284 if (!d->emitCompletedAfterRepaint)
4286 if (!pendingAction)
4287 emit m_part->completed();
4288 else
4289 emit m_part->completed(true);
4294 void KHTMLView::updateScrollBars()
4296 const QWidget *view = widget();
4297 if (!view)
4298 return;
4300 QSize p = viewport()->size();
4301 QSize m = maximumViewportSize();
4303 if (m.expandedTo(view->size()) == m)
4304 p = m; // no scroll bars needed
4306 QSize v = view->size();
4307 horizontalScrollBar()->setRange(0, v.width() - p.width());
4308 horizontalScrollBar()->setPageStep(p.width());
4309 verticalScrollBar()->setRange(0, v.height() - p.height());
4310 verticalScrollBar()->setPageStep(p.height());
4311 if (!d->smoothScrolling) {
4312 d->updateContentsXY();
4316 void KHTMLView::slotMouseScrollTimer()
4318 horizontalScrollBar()->setValue( horizontalScrollBar()->value() +d->m_mouseScroll_byX );
4319 verticalScrollBar()->setValue( verticalScrollBar()->value() +d->m_mouseScroll_byY);
4323 static DOM::Position positionOfLineBoundary(const DOM::Position &pos, bool toEnd)
4325 Selection sel = pos;
4326 sel.expandUsingGranularity(Selection::LINE);
4327 return toEnd ? sel.end() : sel.start();
4330 inline static DOM::Position positionOfLineBegin(const DOM::Position &pos)
4332 return positionOfLineBoundary(pos, false);
4335 inline static DOM::Position positionOfLineEnd(const DOM::Position &pos)
4337 return positionOfLineBoundary(pos, true);
4340 bool KHTMLView::caretKeyPressEvent(QKeyEvent *_ke)
4342 EditorContext *ec = &m_part->d->editor_context;
4343 Selection &caret = ec->m_selection;
4344 Position old_pos = caret.caretPos();
4345 Position pos = old_pos;
4346 bool recalcXPos = true;
4347 bool handled = true;
4349 bool ctrl = _ke->modifiers() & Qt::ControlModifier;
4350 bool shift = _ke->modifiers() & Qt::ShiftModifier;
4352 switch(_ke->key()) {
4354 // -- Navigational keys
4355 case Qt::Key_Down:
4356 pos = old_pos.nextLinePosition(caret.xPosForVerticalArrowNavigation(Selection::EXTENT));
4357 recalcXPos = false;
4358 break;
4360 case Qt::Key_Up:
4361 pos = old_pos.previousLinePosition(caret.xPosForVerticalArrowNavigation(Selection::EXTENT));
4362 recalcXPos = false;
4363 break;
4365 case Qt::Key_Left:
4366 pos = ctrl ? old_pos.previousWordPosition() : old_pos.previousCharacterPosition();
4367 break;
4369 case Qt::Key_Right:
4370 pos = ctrl ? old_pos.nextWordPosition() : old_pos.nextCharacterPosition();
4371 break;
4373 case Qt::Key_PageDown:
4374 // moveCaretNextPage(); ###
4375 break;
4377 case Qt::Key_PageUp:
4378 // moveCaretPrevPage(); ###
4379 break;
4381 case Qt::Key_Home:
4382 if (ctrl)
4383 /*moveCaretToDocumentBoundary(false)*/; // ###
4384 else
4385 pos = positionOfLineBegin(old_pos);
4386 break;
4388 case Qt::Key_End:
4389 if (ctrl)
4390 /*moveCaretToDocumentBoundary(true)*/; // ###
4391 else
4392 pos = positionOfLineEnd(old_pos);
4393 break;
4395 default:
4396 handled = false;
4398 }/*end switch*/
4400 if (pos != old_pos) {
4401 m_part->clearCaretRectIfNeeded();
4403 caret.moveTo(shift ? caret.nonCaretPos() : pos, pos);
4404 int old_x = caret.xPosForVerticalArrowNavigation(Selection::CARETPOS);
4406 m_part->selectionLayoutChanged();
4408 // restore old x-position to prevent recalculation
4409 if (!recalcXPos)
4410 m_part->d->editor_context.m_xPosForVerticalArrowNavigation = old_x;
4412 m_part->emitCaretPositionChanged(pos);
4413 // ### check when to emit it
4414 m_part->notifySelectionChanged();
4418 if (handled) _ke->accept();
4419 return handled;
4422 #undef DEBUG_CARETMODE