From 4e6cfdaaac61d8be13596afcbd4b3c2d2b066591 Mon Sep 17 00:00:00 2001 From: ggarand Date: Sat, 3 Nov 2007 18:24:34 +0000 Subject: [PATCH] more event handling fixes: .introduce more subtle/timed logic to decide how to route wheel events to subwidgets or to the view. It works much more nicely this way as there is no more need for the user to focus an element before being able to scroll using the wheel. This is close to the Gecko behaviour, I'd describe. .properly translate mouse wheel events (gah! that was the deeper reason for most of wheel event problems) .remove test of the widget rectangle for mouse wheel events. As it turns out, I was just turning around the untranslated mouse wheel event bug. .make listbox & textarea widget not propagate wheel events to the view once either end of them has been reached by scrolling. .rewrite & factor out the code computing the widget placement on the root view ; place it in KHTMLWidgetPrivate. git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/KDE/kdelibs@732399 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- khtml/khtmlview.cpp | 37 +++++++++++++++---- khtml/khtmlview.h | 2 +- khtml/rendering/render_form.cpp | 71 ++++++++++++++++++------------------- khtml/rendering/render_form.h | 2 +- khtml/rendering/render_replaced.cpp | 71 ++++++++++++++++++++++++++----------- khtml/rendering/render_replaced.h | 1 + 6 files changed, 119 insertions(+), 65 deletions(-) diff --git a/khtml/khtmlview.cpp b/khtml/khtmlview.cpp index 335273d74..d72e71065 100644 --- a/khtml/khtmlview.cpp +++ b/khtml/khtmlview.cpp @@ -143,6 +143,7 @@ public: m_editorContext = 0; #endif // KHTML_NO_CARET postponed_autorepeat = NULL; + scrollingFromWheelTimerId = 0; reset(); vpolicy = Qt::ScrollBarAsNeeded; hpolicy = Qt::ScrollBarAsNeeded; @@ -208,6 +209,7 @@ public: scrollBarMoved = false; contentsMoving = false; ignoreWheelEvents = false; + scrollingFromWheel = QPoint(-1,-1); borderX = 30; borderY = 30; paged = false; @@ -382,6 +384,8 @@ public: // scrolling activated by MMB short m_mouseScroll_byX; short m_mouseScroll_byY; + QPoint scrollingFromWheel; + int scrollingFromWheelTimerId; QTimer *m_mouseScrollTimer; QWidget *m_mouseScrollIndicator; QPointer m_mouseEventsTarget; @@ -2511,7 +2515,10 @@ void KHTMLView::displayAccessKeys( KHTMLView* caller, KHTMLView* origview, QVect m_part->parentPart()->view()->displayAccessKeys( this, origview, taken, use_fallbacks ); } - +bool KHTMLView::isScrollingFromMouseWheel() const +{ + return d->scrollingFromWheel != QPoint(-1,-1); +} void KHTMLView::accessKeysTimeout() { @@ -3497,6 +3504,11 @@ void KHTMLView::setIgnoreWheelEvents( bool e ) void KHTMLView::wheelEvent(QWheelEvent* e) { + // check if we should reset the state of the indicator describing if + // we are currently scrolling the view as a result of wheel events + if (d->scrollingFromWheel != QPoint(-1,-1) && d->scrollingFromWheel != QCursor::pos()) + d->scrollingFromWheel = d->scrollingFromWheelTimerId ? QCursor::pos() : QPoint(-1,-1); + if (d->accessKeysEnabled && d->accessKeysPreActivate) d->accessKeysPreActivate=false; if ( ( e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier ) @@ -3508,7 +3520,8 @@ void KHTMLView::wheelEvent(QWheelEvent* e) { e->accept(); } - else if( ( (e->orientation() == Qt::Vertical && + else if( !m_kwp->isRedirected() && + ( (e->orientation() == Qt::Vertical && ((d->ignoreWheelEvents && !verticalScrollBar()->isVisible()) || e->delta() > 0 && contentsY() <= 0 || e->delta() < 0 && contentsY() >= contentsHeight() - visibleHeight())) @@ -3525,20 +3538,29 @@ void KHTMLView::wheelEvent(QWheelEvent* e) } else { + int xm = e->x(); + int ym = e->y(); + revertTransforms(xm, ym); + DOM::NodeImpl::MouseEvent mev( e->buttons(), DOM::NodeImpl::MouseWheel ); - m_part->xmlDocImpl()->prepareMouseEvent( false, e->x(), e->y(), &mev ); + m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev ); MouseEventImpl::Orientation o = MouseEventImpl::OVertical; if (e->orientation() == Qt::Horizontal) o = MouseEventImpl::OHorizontal; - - QMouseEvent _mouse(QEvent::MouseMove, e->pos(), Qt::NoButton, e->buttons(), e->modifiers()); + + QMouseEvent _mouse(QEvent::MouseMove, QPoint(xm,ym), Qt::NoButton, e->buttons(), e->modifiers()); bool swallow = dispatchMouseEvent(EventImpl::KHTML_MOUSEWHEEL_EVENT,mev.innerNode.handle(),mev.innerNonSharedNode.handle(), true,-e->delta()/40,&_mouse,true,DOM::NodeImpl::MouseWheel,o); + if (swallow) return; d->scrollBarMoved = true; + d->scrollingFromWheel = QCursor::pos(); + if (d->scrollingFromWheelTimerId) + killTimer(d->scrollingFromWheelTimerId); + d->scrollingFromWheelTimerId = startTimer(200); QScrollArea::wheelEvent( e ); } @@ -3729,7 +3751,10 @@ void KHTMLView::timerEvent ( QTimerEvent *e ) } return; } - else if ( e->timerId() == d->layoutTimerId ) { + else if ( e->timerId() == d->scrollingFromWheelTimerId ) { + killTimer( d->scrollingFromWheelTimerId ); + d->scrollingFromWheelTimerId = 0; + } else if ( e->timerId() == d->layoutTimerId ) { d->dirtyLayout = true; layout(); if (d->firstRelayout) { diff --git a/khtml/khtmlview.h b/khtml/khtmlview.h index cfa7e0c90..64bf493a3 100644 --- a/khtml/khtmlview.h +++ b/khtml/khtmlview.h @@ -405,7 +405,7 @@ private: bool focusNodeWithAccessKey(QChar c, KHTMLView* caller = NULL); QMap< DOM::ElementImpl*, QChar > buildFallbackAccessKeys() const; void displayAccessKeys( KHTMLView* caller, KHTMLView* origview, QVector< QChar >& taken, bool use_fallbacks ); - + bool isScrollingFromMouseWheel() const; void setHasStaticBackground(); void applyTransforms( int& x, int& y, int& w, int& h) const; void revertTransforms( int& x, int& y, int& w, int& h) const; diff --git a/khtml/rendering/render_form.cpp b/khtml/rendering/render_form.cpp index 8d82264e3..187a72008 100644 --- a/khtml/rendering/render_form.cpp +++ b/khtml/rendering/render_form.cpp @@ -364,31 +364,20 @@ public: return; } QPoint p = pw->pos(); - QPoint dest = p + QPoint(0, 500000); - - bool blocked = pw->blockSignals(true); + QPoint dest; QWidget* parent = pw->parentWidget(); - QWidget* v = kwp->m_kwp->renderWidget()->view(); - QWidget* last = 0; - // compute real on-screen position - while (KHTMLWidget*kw = dynamic_cast(v)) { - if (!kw->m_kwp->isRedirected()) { break; } - dest += (v->pos() + QPoint(0, 500000)); - KHTMLView* kv = static_cast(v); - v = kv->part()->parentPart() ? kv->part()->parentPart()->view() : 0; - last = v; - } - if (last) - pw->setParent(last); + KHTMLView* v = kwp->m_kwp->rootViewPos(dest); + bool blocked = pw->blockSignals(true); + if (v != parent) + pw->setParent(v); pw->move( dest ); pw->blockSignals(blocked); KCompletionBox::popup(); blocked = pw->blockSignals(true); - v = kwp->m_kwp->renderWidget()->view(); - if (last) - pw->setParent( parent ); + if (v != parent) + pw->setParent(parent); pw->move( p ); pw->blockSignals(blocked); } @@ -966,6 +955,18 @@ RenderLegend::RenderLegend(HTMLGenericFormElementImpl *element) // ------------------------------------------------------------------------------- +bool ListBoxWidget::event( QEvent * event ) +{ + // accept all wheel events so that they are not propagated to the view + // once either end of the list is reached. + bool ret = KListWidget::event(event); + if (event->type() == QEvent::Wheel) { + event->accept(); + ret = true; + } + return ret; +} + ComboBoxWidget::ComboBoxWidget(QWidget *parent) : KComboBox(false, parent) { @@ -978,31 +979,20 @@ ComboBoxWidget::ComboBoxWidget(QWidget *parent) void ComboBoxWidget::showPopup() { QPoint p = pos(); - QPoint dest = p + QPoint(0, 500000); - - bool blocked = blockSignals(true); + QPoint dest; QWidget* parent = parentWidget(); - QWidget* v = m_kwp->renderWidget()->view(); - QWidget* last = 0; - // compute real on-screen position - while (KHTMLWidget*kw = dynamic_cast(v)) { - if (!kw->m_kwp->isRedirected()) { break; } - dest += (v->pos() + QPoint(0, 500000)); - KHTMLView* kv = static_cast(v); - v = kv->part()->parentPart() ? kv->part()->parentPart()->view() : 0; - last = v; - } - if (last) - setParent(last); + KHTMLView* v = m_kwp->rootViewPos(dest); + bool blocked = blockSignals(true); + if (v != parent) + setParent(v); move( dest ); blockSignals(blocked); KComboBox::showPopup(); blocked = blockSignals(true); - v = m_kwp->renderWidget()->view(); - if (last) - setParent( parent ); + if (v != parent) + setParent(parent); move( p ); blockSignals(blocked); } @@ -1674,7 +1664,14 @@ bool TextAreaWidget::event( QEvent *e ) } } #endif - return KTextEdit::event( e ); + // accept all wheel events so that they are not propagated to the view + // once either end of the widget is reached. + bool ret = KTextEdit::event(e); + if (e->type() == QEvent::Wheel) { + e->accept(); + ret = true; + } + return ret; } // ------------------------------------------------------------------------- diff --git a/khtml/rendering/render_form.h b/khtml/rendering/render_form.h index 675a81551..119bcda85 100644 --- a/khtml/rendering/render_form.h +++ b/khtml/rendering/render_form.h @@ -404,7 +404,7 @@ protected: { viewport()->update(); } - + virtual bool event( QEvent * event ); }; class RenderSelect : public RenderFormElement diff --git a/khtml/rendering/render_replaced.cpp b/khtml/rendering/render_replaced.cpp index a9d045a3b..a20ce65dc 100644 --- a/khtml/rendering/render_replaced.cpp +++ b/khtml/rendering/render_replaced.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -270,8 +271,8 @@ void RenderWidget::setQWidget(QWidget *widget) if (k) k->m_kwp->setRenderWidget(this); connect( m_widget, SIGNAL( destroyed()), this, SLOT( slotWidgetDestructed())); + m_widget->installEventFilter(this); if (isRedirectedWidget()) { - m_widget->installEventFilter(this); if (!qobject_cast(m_widget)) m_widget->setAttribute( Qt::WA_NoSystemBackground ); } @@ -649,7 +650,7 @@ void RenderWidget::paintWidget(PaintInfo& pI, QWidget *widget, int tx, int ty) bool RenderWidget::eventFilter(QObject* /*o*/, QEvent* e) { // no special event processing if this is a frame (in which case KHTMLView handles it all) - if ( qobject_cast( m_widget ) ) + if ( qobject_cast( m_widget ) || isRedirectedWidget() ) return false; if ( !element() ) return true; @@ -695,6 +696,22 @@ bool RenderWidget::eventFilter(QObject* /*o*/, QEvent* e) // if ( ext ) ext->editableWidgetFocused( m_widget ); // } break; + case QEvent::Wheel: { + if (widget()->parentWidget() == view()->widget()) { + bool vertical = ( static_cast(e)->orientation() == Qt::Vertical ); + // don't allow the widget to react to wheel event if + // the view is being scrolled by mouse wheel + // This does not apply if the webpage has no valid scroll range in the given wheel event orientation. + if ( ((vertical && (view()->contentsHeight() > view()->visibleHeight())) || + (!vertical && (view()->contentsWidth() > view()->visibleWidth()))) && + view()->isScrollingFromMouseWheel() ) { + static_cast(e)->ignore(); + QApplication::sendEvent(view(), e); + filtered = true; + } + } + break; + } case QEvent::KeyPress: case QEvent::KeyRelease: // TODO this seems wrong - Qt events are not correctly translated to DOM ones, @@ -867,7 +884,8 @@ bool RenderWidget::handleEvent(const DOM::EventImpl& ev) if (!target || (!::qobject_cast(target) && !::qobject_cast(m_widget))) target = m_widget; - view()->setMouseEventsTarget( target ); + if ( button == Qt::LeftButton ) + view()->setMouseEventsTarget( target ); } else { target = view()->mouseEventsTarget(); if (target) { @@ -883,26 +901,17 @@ bool RenderWidget::handleEvent(const DOM::EventImpl& ev) bool needContextMenuEvent = (type == QMouseEvent::MouseButtonPress && button == Qt::RightButton); bool isMouseWheel = (ev.id() == EventImpl::KHTML_MOUSEWHEEL_EVENT); + if (isMouseWheel) { - // don't allow the widget to react to wheel event unless it's - // currently focused. this avoids accidentally changing a select box - // or something while wheeling a webpage. + // don't allow the widget to react to wheel event if + // a) the view is being scrolled by mouse wheel + // b) it's an unfocused ComboBox (for extra security against unwanted changes to formulars) // This does not apply if the webpage has no valid scroll range in the given wheel event orientation. if ( ((orient == Qt::Vertical && (view()->contentsHeight() > view()->visibleHeight())) || (orient == Qt::Horizontal && (view()->contentsWidth() > view()->visibleWidth()))) && - (!document()->focusNode() || document()->focusNode()->renderer() != this) ) { - ret = false; - break; - } - QPoint gp = QCursor::pos(); - QRect r = target->rect(); - QPoint glob = view()->mapToGlobal( QPoint(0,0) ); - int x, y; - absolutePosition(x,y); - glob.setX( glob.x() + x ); - glob.setY( glob.y() + y ); - r.translate( glob ); - if (!r.contains(gp)) { + ( view()->isScrollingFromMouseWheel() || + (qobject_cast(m_widget) && + (!document()->focusNode() || document()->focusNode()->renderer() != this) ))) { ret = false; break; } @@ -922,7 +931,7 @@ bool RenderWidget::handleEvent(const DOM::EventImpl& ev) QHoverEvent he( QEvent::HoverMove, p, p ); QApplication::sendEvent(target, &he); } - if (ev.id() == EventImpl::MOUSEUP_EVENT && button == Qt::LeftButton) { + if (ev.id() == EventImpl::MOUSEUP_EVENT) { view()->setMouseEventsTarget( 0 ); } delete e; @@ -1036,6 +1045,28 @@ QPoint KHTMLWidgetPrivate::absolutePos() return QPoint(x, y); } +KHTMLView* KHTMLWidgetPrivate::rootViewPos(QPoint& pos) +{ + if (!m_rw || !m_rw->widget()) { + pos = QPoint(); + return 0; + } + pos = absolutePos(); + KHTMLView* v = m_rw->view(); + KHTMLView* last = 0; + while (v) { + last = v; + pos.setX( pos.x() - v->contentsX() ); + pos.setY( pos.y() - v->contentsY() ); + KHTMLWidget*kw = dynamic_cast(v); + if (!kw || !kw->m_kwp->isRedirected()) + break; + pos += kw->m_kwp->absolutePos(); + v = v->part()->parentPart() ? v->part()->parentPart()->view() : 0; + } + return last; +} + // ----------------------------------------------------------------------------- KHTMLWidget::KHTMLWidget() diff --git a/khtml/rendering/render_replaced.h b/khtml/rendering/render_replaced.h index e2c7cd751..742c07582 100644 --- a/khtml/rendering/render_replaced.h +++ b/khtml/rendering/render_replaced.h @@ -170,6 +170,7 @@ class KHTMLWidgetPrivate public: KHTMLWidgetPrivate(): m_rw(0), m_redirected(false) {} QPoint absolutePos(); + KHTMLView* rootViewPos(QPoint& pos); RenderWidget* renderWidget() const { return m_rw; } void setRenderWidget(RenderWidget* rw) { m_rw = rw; } bool isRedirected() const { return m_redirected; } -- 2.11.4.GIT